From 5bd34b8d6f7d1736338bafbbb948565934620e72 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 13 Jul 2026 10:47:33 +0530 Subject: [PATCH 1/8] chore: update Python SDK to 22.0.0 --- CHANGELOG.md | 24 + appwrite/client.py | 13 +- appwrite/encoders/value_class_encoder.py | 26 +- appwrite/enums/billing_plan_group.py | 6 + appwrite/enums/database_status.py | 6 + appwrite/enums/health_antivirus_status.py | 6 - appwrite/enums/health_check_status.py | 5 - appwrite/enums/health_queue_name.py | 16 - appwrite/enums/o_auth2_oidc_prompt.py | 7 + appwrite/enums/o_auth_provider.py | 1 + appwrite/enums/organization_key_scopes.py | 4 + appwrite/enums/project_key_scopes.py | 8 +- appwrite/enums/project_o_auth2_oidc_prompt.py | 7 + appwrite/enums/project_o_auth_provider_id.py | 1 + appwrite/models/__init__.py | 52 +- appwrite/models/activity_event.py | 6 - appwrite/models/additional_resource.py | 30 + appwrite/models/app.py | 3 + appwrite/models/billing_plan.py | 223 +++++ appwrite/models/billing_plan_addon.py | 19 + appwrite/models/billing_plan_addon_details.py | 36 + .../billing_plan_dedicated_database_limits.py | 69 ++ appwrite/models/billing_plan_limits.py | 18 + .../models/billing_plan_supported_addons.py | 21 + appwrite/models/block.py | 3 + appwrite/models/database.py | 4 + appwrite/models/document.py | 11 + appwrite/models/health_antivirus.py | 19 - appwrite/models/health_certificate.py | 30 - appwrite/models/health_status.py | 22 - appwrite/models/health_status_list.py | 19 - appwrite/models/health_time.py | 21 - appwrite/models/locale.py | 33 + appwrite/models/log.py | 2 +- appwrite/models/o_auth2_appwrite.py | 24 + appwrite/models/o_auth2_oidc.py | 7 + appwrite/models/o_auth2_provider_list.py | 5 +- appwrite/models/oauth2_grant.py | 3 + ...health_queue.py => oauth2_organization.py} | 10 +- appwrite/models/oauth2_organization_list.py | 19 + appwrite/models/oauth2_par.py | 18 + appwrite/models/oauth2_project.py | 21 + appwrite/models/oauth2_project_list.py | 19 + appwrite/models/organization.py | 132 +++ appwrite/models/preferences.py | 11 + appwrite/models/program.py | 39 + appwrite/models/project.py | 6 + appwrite/models/row.py | 11 + appwrite/models/usage_billing_plan.py | 46 + appwrite/models/usage_data_point.py | 60 -- appwrite/models/usage_event_list.py | 19 - appwrite/models/usage_gauge_list.py | 19 - appwrite/models/usage_metric.py | 19 - appwrite/query.py | 12 + appwrite/services/account.py | 2 +- appwrite/services/apps.py | 47 + appwrite/services/backups.py | 10 +- appwrite/services/functions.py | 25 +- appwrite/services/health.py | 882 ------------------ appwrite/services/messaging.py | 197 ---- appwrite/services/oauth2.py | 290 +++++- appwrite/services/organization.py | 357 ++++++- appwrite/services/project.py | 68 +- appwrite/services/sites.py | 14 +- appwrite/services/tables_db.py | 10 +- appwrite/services/usage.py | 202 ---- appwrite/services/users.py | 2 +- .../update-labels.md} | 11 +- docs/examples/backups/create-restoration.md | 3 +- .../functions/get-deployment-download.md | 3 +- .../examples/functions/list-specifications.md | 4 +- docs/examples/health/get-antivirus.md | 16 - docs/examples/health/get-certificate.md | 18 - docs/examples/health/get-db.md | 16 - docs/examples/health/get-failed-jobs.md | 20 - docs/examples/health/get-pub-sub.md | 16 - .../examples/health/get-queue-certificates.md | 18 - docs/examples/health/get-queue-databases.md | 19 - docs/examples/health/get-queue-deletes.md | 18 - docs/examples/health/get-queue-functions.md | 18 - docs/examples/health/get-queue-logs.md | 18 - docs/examples/health/get-queue-mails.md | 18 - docs/examples/health/get-queue-messaging.md | 18 - docs/examples/health/get-queue-migrations.md | 18 - .../health/get-queue-stats-resources.md | 18 - docs/examples/health/get-queue-usage.md | 18 - docs/examples/health/get-queue-webhooks.md | 18 - docs/examples/health/get-storage-local.md | 16 - docs/examples/health/get-storage.md | 16 - docs/examples/messaging/list-provider-logs.md | 20 - .../messaging/list-subscriber-logs.md | 20 - docs/examples/messaging/list-topic-logs.md | 20 - docs/examples/oauth2/approve.md | 3 +- docs/examples/oauth2/authorize.md | 13 +- .../oauth2/create-device-authorization.md | 4 +- docs/examples/oauth2/create-par.md | 30 + docs/examples/oauth2/create-token.md | 4 +- docs/examples/oauth2/list-organizations.md | 20 + docs/examples/oauth2/list-projects.md | 20 + .../organization/create-membership.md | 23 + .../delete-membership.md} | 11 +- .../get-time.md => organization/delete.md} | 9 +- .../get-membership.md} | 10 +- .../get-cache.md => organization/get.md} | 8 +- .../list-memberships.md} | 10 +- .../update-membership.md} | 12 +- .../update.md} | 10 +- .../project/update-o-auth-2-appwrite.md | 20 + docs/examples/project/update-o-auth-2-oidc.md | 3 + .../project/update-o-auth-2-server.md | 3 +- docs/examples/sites/list-specifications.md | 4 +- docs/examples/tablesdb/create.md | 2 +- docs/examples/usage/list-events.md | 28 - docs/examples/usage/list-gauges.md | 28 - pyproject.toml | 2 +- requirements.txt | 2 +- setup.py | 4 +- test/services/test_activities.py | 4 +- test/services/test_apps.py | 42 + test/services/test_backups.py | 4 +- test/services/test_health.py | 361 ------- test/services/test_messaging.py | 60 -- test/services/test_oauth2.py | 51 +- test/services/test_organization.py | 576 ++++++++++++ test/services/test_project.py | 39 +- test/services/test_usage.py | 45 - 126 files changed, 2633 insertions(+), 2607 deletions(-) create mode 100644 appwrite/enums/billing_plan_group.py create mode 100644 appwrite/enums/database_status.py delete mode 100644 appwrite/enums/health_antivirus_status.py delete mode 100644 appwrite/enums/health_check_status.py delete mode 100644 appwrite/enums/health_queue_name.py create mode 100644 appwrite/enums/o_auth2_oidc_prompt.py create mode 100644 appwrite/enums/project_o_auth2_oidc_prompt.py create mode 100644 appwrite/models/additional_resource.py create mode 100644 appwrite/models/billing_plan.py create mode 100644 appwrite/models/billing_plan_addon.py create mode 100644 appwrite/models/billing_plan_addon_details.py create mode 100644 appwrite/models/billing_plan_dedicated_database_limits.py create mode 100644 appwrite/models/billing_plan_limits.py create mode 100644 appwrite/models/billing_plan_supported_addons.py delete mode 100644 appwrite/models/health_antivirus.py delete mode 100644 appwrite/models/health_certificate.py delete mode 100644 appwrite/models/health_status.py delete mode 100644 appwrite/models/health_status_list.py delete mode 100644 appwrite/models/health_time.py create mode 100644 appwrite/models/o_auth2_appwrite.py rename appwrite/models/{health_queue.py => oauth2_organization.py} (55%) create mode 100644 appwrite/models/oauth2_organization_list.py create mode 100644 appwrite/models/oauth2_par.py create mode 100644 appwrite/models/oauth2_project.py create mode 100644 appwrite/models/oauth2_project_list.py create mode 100644 appwrite/models/organization.py create mode 100644 appwrite/models/program.py create mode 100644 appwrite/models/usage_billing_plan.py delete mode 100644 appwrite/models/usage_data_point.py delete mode 100644 appwrite/models/usage_event_list.py delete mode 100644 appwrite/models/usage_gauge_list.py delete mode 100644 appwrite/models/usage_metric.py delete mode 100644 appwrite/services/health.py delete mode 100644 appwrite/services/usage.py rename docs/examples/{health/get-audits-db.md => apps/update-labels.md} (63%) delete mode 100644 docs/examples/health/get-antivirus.md delete mode 100644 docs/examples/health/get-certificate.md delete mode 100644 docs/examples/health/get-db.md delete mode 100644 docs/examples/health/get-failed-jobs.md delete mode 100644 docs/examples/health/get-pub-sub.md delete mode 100644 docs/examples/health/get-queue-certificates.md delete mode 100644 docs/examples/health/get-queue-databases.md delete mode 100644 docs/examples/health/get-queue-deletes.md delete mode 100644 docs/examples/health/get-queue-functions.md delete mode 100644 docs/examples/health/get-queue-logs.md delete mode 100644 docs/examples/health/get-queue-mails.md delete mode 100644 docs/examples/health/get-queue-messaging.md delete mode 100644 docs/examples/health/get-queue-migrations.md delete mode 100644 docs/examples/health/get-queue-stats-resources.md delete mode 100644 docs/examples/health/get-queue-usage.md delete mode 100644 docs/examples/health/get-queue-webhooks.md delete mode 100644 docs/examples/health/get-storage-local.md delete mode 100644 docs/examples/health/get-storage.md delete mode 100644 docs/examples/messaging/list-provider-logs.md delete mode 100644 docs/examples/messaging/list-subscriber-logs.md delete mode 100644 docs/examples/messaging/list-topic-logs.md create mode 100644 docs/examples/oauth2/create-par.md create mode 100644 docs/examples/oauth2/list-organizations.md create mode 100644 docs/examples/oauth2/list-projects.md create mode 100644 docs/examples/organization/create-membership.md rename docs/examples/{health/get.md => organization/delete-membership.md} (60%) rename docs/examples/{health/get-time.md => organization/delete.md} (60%) rename docs/examples/{health/get-queue-audits.md => organization/get-membership.md} (57%) rename docs/examples/{health/get-cache.md => organization/get.md} (59%) rename docs/examples/{messaging/list-message-logs.md => organization/list-memberships.md} (60%) rename docs/examples/{health/get-console-pausing.md => organization/update-membership.md} (55%) rename docs/examples/{health/get-queue-builds.md => organization/update.md} (56%) create mode 100644 docs/examples/project/update-o-auth-2-appwrite.md delete mode 100644 docs/examples/usage/list-events.md delete mode 100644 docs/examples/usage/list-gauges.md delete mode 100644 test/services/test_health.py delete mode 100644 test/services/test_usage.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c8e7a93d..e66e10b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Change Log +## 22.0.0 + +* Breaking: removed `Health` service and all health models and enums +* Breaking: removed `Usage` service and `UsageMetric`, `UsageDataPoint`, `UsageEventList`, `UsageGaugeList` models +* Breaking: removed messaging `list_message_logs`, `list_provider_logs`, `list_subscriber_logs`, `list_topic_logs` methods +* Breaking: renamed `tables_db.create` parameter `dedicated_database_id` to `specification` +* Breaking: removed `countryCode` and `countryName` from `ActivityEvent` model +* Added: `Client.set_bearer` for OAuth access token authentication +* Added: `organization` service `get`, `update`, `delete` and membership CRUD methods +* Added: `oauth2` service `create_par`, `list_organizations`, `list_projects` methods +* Added: `resource`, `audience`, `request_uri` OAuth2 parameters; `scope` on `approve` +* Added: `appwrite` OAuth provider, `OAuth2Appwrite` model, `update_o_auth2_appwrite` method +* Added: `Query.vector_dot`, `Query.vector_cosine`, `Query.vector_euclidean` methods +* Added: `apps.update_labels` method and `labels` field on `App` model +* Added: `new_specification` parameter to `backups.create_restoration` +* Added: `type` parameter to `functions` and `sites` `list_specifications` +* Added: `token` parameter to `functions.get_deployment_download` +* Added: `prompt` and `max_age` parameters to `update_o_auth2_oidc`; `default_scopes` to `update_o_auth2_server` +* Added: `BillingPlan`, `Organization`, `Program`, `Oauth2PAR`, `DatabaseStatus`, `BillingPlanGroup` models and enums +* Added: `status` on `Database`, `mode` on `Block`, `onboarding` on `Project`, geolocation fields on `Locale` +* Added: organization, `project.oauth2`, and `stages` key scope enum values +* Fixed: booleans now serialized as `true`/`false` in flattened query parameters +* Fixed: `model_dump` on `Document`, `Row`, `Preferences` now includes dynamic `data` + ## 21.0.0 * Added: `apps` service for managing apps and app secrets diff --git a/appwrite/client.py b/appwrite/client.py index baafb0e7..83f61a8f 100644 --- a/appwrite/client.py +++ b/appwrite/client.py @@ -17,11 +17,11 @@ def __init__(self): self._endpoint = 'https://cloud.appwrite.io/v1' self._global_headers = { 'content-type': '', - 'user-agent' : f'AppwritePythonSDK/21.0.0 ({platform.uname().system}; {platform.uname().version}; {platform.uname().machine})', + 'user-agent' : f'AppwritePythonSDK/22.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': '21.0.0', + 'x-sdk-version': '22.0.0', 'X-Appwrite-Response-Format' : '1.9.5', } self._config = {} @@ -67,6 +67,13 @@ def set_jwt(self, value): self._config['jwt'] = value return self + def set_bearer(self, value): + """The OAuth access token to authenticate with""" + + self._global_headers['authorization'] = value + self._config['bearer'] = value + return self + def set_locale(self, value): self._global_headers['x-appwrite-locale'] = value self._config['locale'] = value @@ -341,6 +348,8 @@ def flatten(self, data, prefix='', stringify=False): if isinstance(value, list) or isinstance(value, dict): output = {**output, **self.flatten(value, finalKey, stringify)} + elif isinstance(value, bool): + output[finalKey] = 'true' if value else 'false' else: if stringify: output[finalKey] = str(value) diff --git a/appwrite/encoders/value_class_encoder.py b/appwrite/encoders/value_class_encoder.py index 5a4914b1..b725fb9b 100644 --- a/appwrite/encoders/value_class_encoder.py +++ b/appwrite/encoders/value_class_encoder.py @@ -21,13 +21,13 @@ from ..enums.vcs_reference_type import VCSReferenceType from ..enums.deployment_download_type import DeploymentDownloadType from ..enums.execution_method import ExecutionMethod -from ..enums.health_queue_name import HealthQueueName from ..enums.message_priority import MessagePriority from ..enums.smtp_encryption import SmtpEncryption from ..enums.organization_key_scopes import OrganizationKeyScopes from ..enums.region import Region from ..enums.project_auth_method_id import ProjectAuthMethodId from ..enums.project_o_auth2_google_prompt import ProjectOAuth2GooglePrompt +from ..enums.project_o_auth2_oidc_prompt import ProjectOAuth2OidcPrompt from ..enums.project_o_auth_provider_id import ProjectOAuthProviderId from ..enums.project_policy_id import ProjectPolicyId from ..enums.project_protocol_id import ProjectProtocolId @@ -46,6 +46,7 @@ from ..enums.password_hash import PasswordHash from ..enums.messaging_provider_type import MessagingProviderType from ..enums.database_type import DatabaseType +from ..enums.database_status import DatabaseStatus from ..enums.attribute_status import AttributeStatus from ..enums.column_status import ColumnStatus from ..enums.index_status import IndexStatus @@ -53,12 +54,12 @@ from ..enums.execution_trigger import ExecutionTrigger from ..enums.execution_status import ExecutionStatus from ..enums.o_auth2_google_prompt import OAuth2GooglePrompt +from ..enums.o_auth2_oidc_prompt import OAuth2OidcPrompt from ..enums.platform_type import PlatformType -from ..enums.health_antivirus_status import HealthAntivirusStatus -from ..enums.health_check_status import HealthCheckStatus from ..enums.proxy_rule_deployment_resource_type import ProxyRuleDeploymentResourceType from ..enums.proxy_rule_status import ProxyRuleStatus from ..enums.message_status import MessageStatus +from ..enums.billing_plan_group import BillingPlanGroup class ValueClassEncoder(json.JSONEncoder): def default(self, o): @@ -128,9 +129,6 @@ def default(self, o): if isinstance(o, ExecutionMethod): return o.value - if isinstance(o, HealthQueueName): - return o.value - if isinstance(o, MessagePriority): return o.value @@ -149,6 +147,9 @@ def default(self, o): if isinstance(o, ProjectOAuth2GooglePrompt): return o.value + if isinstance(o, ProjectOAuth2OidcPrompt): + return o.value + if isinstance(o, ProjectOAuthProviderId): return o.value @@ -203,6 +204,9 @@ def default(self, o): if isinstance(o, DatabaseType): return o.value + if isinstance(o, DatabaseStatus): + return o.value + if isinstance(o, AttributeStatus): return o.value @@ -224,13 +228,10 @@ def default(self, o): if isinstance(o, OAuth2GooglePrompt): return o.value - if isinstance(o, PlatformType): - return o.value - - if isinstance(o, HealthAntivirusStatus): + if isinstance(o, OAuth2OidcPrompt): return o.value - if isinstance(o, HealthCheckStatus): + if isinstance(o, PlatformType): return o.value if isinstance(o, ProxyRuleDeploymentResourceType): @@ -242,4 +243,7 @@ def default(self, o): if isinstance(o, MessageStatus): return o.value + if isinstance(o, BillingPlanGroup): + return o.value + return super().default(o) diff --git a/appwrite/enums/billing_plan_group.py b/appwrite/enums/billing_plan_group.py new file mode 100644 index 00000000..5a659196 --- /dev/null +++ b/appwrite/enums/billing_plan_group.py @@ -0,0 +1,6 @@ +from enum import Enum + +class BillingPlanGroup(Enum): + STARTER = "starter" + PRO = "pro" + SCALE = "scale" diff --git a/appwrite/enums/database_status.py b/appwrite/enums/database_status.py new file mode 100644 index 00000000..7c5f888f --- /dev/null +++ b/appwrite/enums/database_status.py @@ -0,0 +1,6 @@ +from enum import Enum + +class DatabaseStatus(Enum): + PROVISIONING = "provisioning" + READY = "ready" + FAILED = "failed" diff --git a/appwrite/enums/health_antivirus_status.py b/appwrite/enums/health_antivirus_status.py deleted file mode 100644 index 38c1684b..00000000 --- a/appwrite/enums/health_antivirus_status.py +++ /dev/null @@ -1,6 +0,0 @@ -from enum import Enum - -class HealthAntivirusStatus(Enum): - DISABLED = "disabled" - OFFLINE = "offline" - ONLINE = "online" diff --git a/appwrite/enums/health_check_status.py b/appwrite/enums/health_check_status.py deleted file mode 100644 index 2cc9c113..00000000 --- a/appwrite/enums/health_check_status.py +++ /dev/null @@ -1,5 +0,0 @@ -from enum import Enum - -class HealthCheckStatus(Enum): - PASS = "pass" - FAIL = "fail" diff --git a/appwrite/enums/health_queue_name.py b/appwrite/enums/health_queue_name.py deleted file mode 100644 index a04ab15f..00000000 --- a/appwrite/enums/health_queue_name.py +++ /dev/null @@ -1,16 +0,0 @@ -from enum import Enum - -class HealthQueueName(Enum): - V1_DATABASE = "v1-database" - V1_DELETES = "v1-deletes" - V1_AUDITS = "v1-audits" - V1_MAILS = "v1-mails" - V1_FUNCTIONS = "v1-functions" - V1_STATS_RESOURCES = "v1-stats-resources" - V1_STATS_USAGE = "v1-stats-usage" - V1_WEBHOOKS = "v1-webhooks" - V1_CERTIFICATES = "v1-certificates" - V1_BUILDS = "v1-builds" - V1_SCREENSHOTS = "v1-screenshots" - V1_MESSAGING = "v1-messaging" - V1_MIGRATIONS = "v1-migrations" diff --git a/appwrite/enums/o_auth2_oidc_prompt.py b/appwrite/enums/o_auth2_oidc_prompt.py new file mode 100644 index 00000000..0c21c203 --- /dev/null +++ b/appwrite/enums/o_auth2_oidc_prompt.py @@ -0,0 +1,7 @@ +from enum import Enum + +class OAuth2OidcPrompt(Enum): + NONE = "none" + LOGIN = "login" + CONSENT = "consent" + SELECT_ACCOUNT = "select_account" diff --git a/appwrite/enums/o_auth_provider.py b/appwrite/enums/o_auth_provider.py index 18b69a49..e0aa4ea5 100644 --- a/appwrite/enums/o_auth_provider.py +++ b/appwrite/enums/o_auth_provider.py @@ -3,6 +3,7 @@ class OAuthProvider(Enum): AMAZON = "amazon" APPLE = "apple" + APPWRITE = "appwrite" AUTH0 = "auth0" AUTHENTIK = "authentik" AUTODESK = "autodesk" diff --git a/appwrite/enums/organization_key_scopes.py b/appwrite/enums/organization_key_scopes.py index aa788f1a..ab4e4526 100644 --- a/appwrite/enums/organization_key_scopes.py +++ b/appwrite/enums/organization_key_scopes.py @@ -7,6 +7,10 @@ class OrganizationKeyScopes(Enum): DEVKEYS_WRITE = "devKeys.write" ORGANIZATION_KEYS_READ = "organization.keys.read" ORGANIZATION_KEYS_WRITE = "organization.keys.write" + ORGANIZATION_MEMBERSHIPS_READ = "organization.memberships.read" + ORGANIZATION_MEMBERSHIPS_WRITE = "organization.memberships.write" + ORGANIZATION_READ = "organization.read" + ORGANIZATION_WRITE = "organization.write" DOMAINS_READ = "domains.read" DOMAINS_WRITE = "domains.write" KEYS_READ = "keys.read" diff --git a/appwrite/enums/project_key_scopes.py b/appwrite/enums/project_key_scopes.py index a0356058..10099119 100644 --- a/appwrite/enums/project_key_scopes.py +++ b/appwrite/enums/project_key_scopes.py @@ -13,10 +13,12 @@ class ProjectKeyScopes(Enum): POLICIES_WRITE = "policies.write" PROJECT_POLICIES_READ = "project.policies.read" PROJECT_POLICIES_WRITE = "project.policies.write" + PROJECT_OAUTH2_READ = "project.oauth2.read" + PROJECT_OAUTH2_WRITE = "project.oauth2.write" TEMPLATES_READ = "templates.read" TEMPLATES_WRITE = "templates.write" - OAUTH2_READ = "oauth2.read" - OAUTH2_WRITE = "oauth2.write" + STAGES_READ = "stages.read" + STAGES_WRITE = "stages.write" USERS_READ = "users.read" USERS_WRITE = "users.write" SESSIONS_READ = "sessions.read" @@ -97,4 +99,6 @@ class ProjectKeyScopes(Enum): EVENTS_READ = "events.read" APPS_READ = "apps.read" APPS_WRITE = "apps.write" + OAUTH2_READ = "oauth2.read" + OAUTH2_WRITE = "oauth2.write" USAGE_READ = "usage.read" diff --git a/appwrite/enums/project_o_auth2_oidc_prompt.py b/appwrite/enums/project_o_auth2_oidc_prompt.py new file mode 100644 index 00000000..43c64713 --- /dev/null +++ b/appwrite/enums/project_o_auth2_oidc_prompt.py @@ -0,0 +1,7 @@ +from enum import Enum + +class ProjectOAuth2OidcPrompt(Enum): + NONE = "none" + LOGIN = "login" + CONSENT = "consent" + SELECT_ACCOUNT = "select_account" diff --git a/appwrite/enums/project_o_auth_provider_id.py b/appwrite/enums/project_o_auth_provider_id.py index 71ba1213..c61f6002 100644 --- a/appwrite/enums/project_o_auth_provider_id.py +++ b/appwrite/enums/project_o_auth_provider_id.py @@ -3,6 +3,7 @@ class ProjectOAuthProviderId(Enum): AMAZON = "amazon" APPLE = "apple" + APPWRITE = "appwrite" AUTH0 = "auth0" AUTHENTIK = "authentik" AUTODESK = "autodesk" diff --git a/appwrite/models/__init__.py b/appwrite/models/__init__.py index e8267dd9..8e75504a 100644 --- a/appwrite/models/__init__.py +++ b/appwrite/models/__init__.py @@ -34,7 +34,6 @@ from .mock_number_list import MockNumberList from .policy_list import PolicyList from .email_template_list import EmailTemplateList -from .health_status_list import HealthStatusList from .proxy_rule_list import ProxyRuleList from .locale_code_list import LocaleCodeList from .provider_list import ProviderList @@ -160,6 +159,7 @@ from .o_auth2_tradeshift import OAuth2Tradeshift from .o_auth2_paypal import OAuth2Paypal from .o_auth2_gitlab import OAuth2Gitlab +from .o_auth2_appwrite import OAuth2Appwrite from .o_auth2_authentik import OAuth2Authentik from .o_auth2_auth0 import OAuth2Auth0 from .o_auth2_fusion_auth import OAuth2FusionAuth @@ -192,11 +192,6 @@ from .language import Language from .currency import Currency from .phone import Phone -from .health_antivirus import HealthAntivirus -from .health_queue import HealthQueue -from .health_status import HealthStatus -from .health_certificate import HealthCertificate -from .health_time import HealthTime from .headers import Headers from .specification import Specification from .proxy_rule import ProxyRule @@ -215,19 +210,25 @@ from .insight_cta import InsightCTA from .report import Report from .activity_event import ActivityEvent +from .additional_resource import AdditionalResource from .backup_archive import BackupArchive from .billing_limits import BillingLimits +from .billing_plan import BillingPlan +from .billing_plan_addon import BillingPlanAddon +from .billing_plan_addon_details import BillingPlanAddonDetails +from .billing_plan_limits import BillingPlanLimits +from .billing_plan_dedicated_database_limits import BillingPlanDedicatedDatabaseLimits +from .billing_plan_supported_addons import BillingPlanSupportedAddons from .block import Block +from .organization import Organization from .backup_policy import BackupPolicy from .policy_deny_aliased_email import PolicyDenyAliasedEmail from .policy_deny_disposable_email import PolicyDenyDisposableEmail from .policy_deny_free_email import PolicyDenyFreeEmail from .policy_deny_corporate_email import PolicyDenyCorporateEmail +from .program import Program from .backup_restoration import BackupRestoration -from .usage_data_point import UsageDataPoint -from .usage_event_list import UsageEventList -from .usage_gauge_list import UsageGaugeList -from .usage_metric import UsageMetric +from .usage_billing_plan import UsageBillingPlan from .app import App from .app_secret import AppSecret from .app_secret_plaintext import AppSecretPlaintext @@ -236,7 +237,12 @@ from .oauth2_reject import Oauth2Reject from .oauth2_grant import Oauth2Grant from .oauth2_device_authorization import Oauth2DeviceAuthorization +from .oauth2_par import Oauth2PAR from .oauth2_token import Oauth2Token +from .oauth2_project import Oauth2Project +from .oauth2_organization import Oauth2Organization +from .oauth2_project_list import Oauth2ProjectList +from .oauth2_organization_list import Oauth2OrganizationList from .activity_event_list import ActivityEventList from .backup_archive_list import BackupArchiveList from .backup_policy_list import BackupPolicyList @@ -281,7 +287,6 @@ 'MockNumberList', 'PolicyList', 'EmailTemplateList', - 'HealthStatusList', 'ProxyRuleList', 'LocaleCodeList', 'ProviderList', @@ -407,6 +412,7 @@ 'OAuth2Tradeshift', 'OAuth2Paypal', 'OAuth2Gitlab', + 'OAuth2Appwrite', 'OAuth2Authentik', 'OAuth2Auth0', 'OAuth2FusionAuth', @@ -439,11 +445,6 @@ 'Language', 'Currency', 'Phone', - 'HealthAntivirus', - 'HealthQueue', - 'HealthStatus', - 'HealthCertificate', - 'HealthTime', 'Headers', 'Specification', 'ProxyRule', @@ -462,19 +463,25 @@ 'InsightCTA', 'Report', 'ActivityEvent', + 'AdditionalResource', 'BackupArchive', 'BillingLimits', + 'BillingPlan', + 'BillingPlanAddon', + 'BillingPlanAddonDetails', + 'BillingPlanLimits', + 'BillingPlanDedicatedDatabaseLimits', + 'BillingPlanSupportedAddons', 'Block', + 'Organization', 'BackupPolicy', 'PolicyDenyAliasedEmail', 'PolicyDenyDisposableEmail', 'PolicyDenyFreeEmail', 'PolicyDenyCorporateEmail', + 'Program', 'BackupRestoration', - 'UsageDataPoint', - 'UsageEventList', - 'UsageGaugeList', - 'UsageMetric', + 'UsageBillingPlan', 'App', 'AppSecret', 'AppSecretPlaintext', @@ -483,7 +490,12 @@ 'Oauth2Reject', 'Oauth2Grant', 'Oauth2DeviceAuthorization', + 'Oauth2PAR', 'Oauth2Token', + 'Oauth2Project', + 'Oauth2Organization', + 'Oauth2ProjectList', + 'Oauth2OrganizationList', 'ActivityEventList', 'BackupArchiveList', 'BackupPolicyList', diff --git a/appwrite/models/activity_event.py b/appwrite/models/activity_event.py index 9e2b6313..11265c1d 100644 --- a/appwrite/models/activity_event.py +++ b/appwrite/models/activity_event.py @@ -45,10 +45,6 @@ class ActivityEvent(AppwriteModel): Team ID. hostname : str Hostname. - countrycode : str - Country two-character ISO 3166-1 alpha code. - countryname : str - Country name. """ id: str = Field(..., alias='$id') actortype: str = Field(..., alias='actorType') @@ -68,5 +64,3 @@ class ActivityEvent(AppwriteModel): projectid: str = Field(..., alias='projectId') teamid: str = Field(..., alias='teamId') hostname: str = Field(..., alias='hostname') - countrycode: str = Field(..., alias='countryCode') - countryname: str = Field(..., alias='countryName') diff --git a/appwrite/models/additional_resource.py b/appwrite/models/additional_resource.py new file mode 100644 index 00000000..63e01170 --- /dev/null +++ b/appwrite/models/additional_resource.py @@ -0,0 +1,30 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class AdditionalResource(AppwriteModel): + """ + AdditionalResource + + Attributes + ---------- + name : str + Resource name + unit : str + Resource unit + currency : str + Price currency + price : float + Price + value : float + Resource value + invoicedesc : str + Description on invoice + """ + name: str = Field(..., alias='name') + unit: str = Field(..., alias='unit') + currency: str = Field(..., alias='currency') + price: float = Field(..., alias='price') + value: float = Field(..., alias='value') + invoicedesc: str = Field(..., alias='invoiceDesc') diff --git a/appwrite/models/app.py b/appwrite/models/app.py index 189910d2..f570a66c 100644 --- a/appwrite/models/app.py +++ b/appwrite/models/app.py @@ -34,6 +34,8 @@ class App(AppwriteModel): Application tagline shown to users during OAuth2 consent. tags : List[Any] Application tags shown to users during OAuth2 consent. + labels : List[Any] + Application labels. Read-only for clients; only a server SDK using a project API key can update them. images : List[Any] Application image URLs shown to users during OAuth2 consent. supporturl : str @@ -69,6 +71,7 @@ class App(AppwriteModel): contacts: List[Any] = Field(..., alias='contacts') tagline: str = Field(..., alias='tagline') tags: List[Any] = Field(..., alias='tags') + labels: List[Any] = Field(..., alias='labels') images: List[Any] = Field(..., alias='images') supporturl: str = Field(..., alias='supportUrl') datadeletionurl: str = Field(..., alias='dataDeletionUrl') diff --git a/appwrite/models/billing_plan.py b/appwrite/models/billing_plan.py new file mode 100644 index 00000000..64db521b --- /dev/null +++ b/appwrite/models/billing_plan.py @@ -0,0 +1,223 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .usage_billing_plan import UsageBillingPlan +from .billing_plan_addon import BillingPlanAddon +from .billing_plan_supported_addons import BillingPlanSupportedAddons +from .billing_plan_limits import BillingPlanLimits +from ..enums.billing_plan_group import BillingPlanGroup +from .program import Program +from .billing_plan_dedicated_database_limits import BillingPlanDedicatedDatabaseLimits + +class BillingPlan(AppwriteModel): + """ + billingPlan + + Attributes + ---------- + id : str + Plan ID. + name : str + Plan name + desc : str + Plan description + order : float + Plan order + price : float + Price + trial : float + Trial days + bandwidth : float + Bandwidth + storage : float + Storage + imagetransformations : float + Image Transformations + screenshotsgenerated : float + Screenshots generated + members : float + Members + webhooks : float + Webhooks + projects : float + Projects + platforms : float + Platforms + users : float + Users + teams : float + Teams + databases : float + Databases + databasesreads : float + Database reads per month + databaseswrites : float + Database writes per month + databasesbatchsize : float + Database batch size limit + buckets : float + Buckets + filesize : float + File size + functions : float + Functions + sites : float + Sites + executions : float + Function executions + executionsretentioncount : float + Rolling max executions retained per function/site + gbhours : float + GB hours for functions + realtime : float + Realtime connections + realtimemessages : float + Realtime messages + messages : float + Messages per month + topics : float + Topics for messaging + authphone : float + SMS authentications per month + domains : float + Custom domains + activitylogs : float + Activity log days + usagelogs : float + Usage history days + usagelogsintervals : Optional[List[Any]] + Usage log time intervals allowed for this plan (e.g. 15m, 1h, 1d). + projectinactivitydays : float + Number of days of console inactivity before a project is paused. 0 means pausing is disabled. + alertlimit : float + Alert threshold percentage + usage : UsageBillingPlan + Additional resources + addons : BillingPlanAddon + Addons + budgetcapenabled : bool + Budget cap enabled or disabled. + customsmtp : bool + Custom SMTP + emailbranding : bool + Appwrite branding in email + requirespaymentmethod : bool + Does plan require payment method + requiresbillingaddress : bool + Does plan require billing address + isavailable : bool + Is the billing plan available + selfservice : bool + Can user change the plan themselves + premiumsupport : bool + Does plan enable premium support + budgeting : bool + Does plan support budget cap + supportsmocknumbers : bool + Does plan support mock numbers + supportsorganizationroles : bool + Does plan support organization roles + supportscredits : bool + Does plan support credit + supportsdisposableemailvalidation : bool + Does plan support blocking disposable email addresses. + supportscanonicalemailvalidation : bool + Does plan support requiring canonical email addresses. + supportsfreeemailvalidation : bool + Does plan support blocking free email addresses. + supportscorporateemailvalidation : bool + Does plan support restricting sign-ups to corporate email addresses only. + supportsprojectspecificroles : bool + Does plan support project-specific member roles. + backupsenabled : bool + Does plan support backup policies. + usageperproject : bool + Whether usage addons are calculated per project. + supportedaddons : BillingPlanSupportedAddons + Supported addons for this plan + backuppolicies : float + How many policies does plan support + deploymentsize : float + Maximum function and site deployment size in MB + buildsize : float + Maximum function and site deployment size in MB + databasesallowencrypt : bool + Does the plan support encrypted string attributes or not. + limits : Optional[BillingPlanLimits] + Plan specific limits + group : BillingPlanGroup + Group of this billing plan for variants + program : Optional[Program] + Details of the program this plan is a part of. + dedicateddatabases : Optional[BillingPlanDedicatedDatabaseLimits] + Dedicated database limits available to this plan. + """ + id: str = Field(..., alias='$id') + name: str = Field(..., alias='name') + desc: str = Field(..., alias='desc') + order: float = Field(..., alias='order') + price: float = Field(..., alias='price') + trial: float = Field(..., alias='trial') + bandwidth: float = Field(..., alias='bandwidth') + storage: float = Field(..., alias='storage') + imagetransformations: float = Field(..., alias='imageTransformations') + screenshotsgenerated: float = Field(..., alias='screenshotsGenerated') + members: float = Field(..., alias='members') + webhooks: float = Field(..., alias='webhooks') + projects: float = Field(..., alias='projects') + platforms: float = Field(..., alias='platforms') + users: float = Field(..., alias='users') + teams: float = Field(..., alias='teams') + databases: float = Field(..., alias='databases') + databasesreads: float = Field(..., alias='databasesReads') + databaseswrites: float = Field(..., alias='databasesWrites') + databasesbatchsize: float = Field(..., alias='databasesBatchSize') + buckets: float = Field(..., alias='buckets') + filesize: float = Field(..., alias='fileSize') + functions: float = Field(..., alias='functions') + sites: float = Field(..., alias='sites') + executions: float = Field(..., alias='executions') + executionsretentioncount: float = Field(..., alias='executionsRetentionCount') + gbhours: float = Field(..., alias='GBHours') + realtime: float = Field(..., alias='realtime') + realtimemessages: float = Field(..., alias='realtimeMessages') + messages: float = Field(..., alias='messages') + topics: float = Field(..., alias='topics') + authphone: float = Field(..., alias='authPhone') + domains: float = Field(..., alias='domains') + activitylogs: float = Field(..., alias='activityLogs') + usagelogs: float = Field(..., alias='usageLogs') + usagelogsintervals: Optional[List[Any]] = Field(default=None, alias='usageLogsIntervals') + projectinactivitydays: float = Field(..., alias='projectInactivityDays') + alertlimit: float = Field(..., alias='alertLimit') + usage: UsageBillingPlan = Field(..., alias='usage') + addons: BillingPlanAddon = Field(..., alias='addons') + budgetcapenabled: bool = Field(..., alias='budgetCapEnabled') + customsmtp: bool = Field(..., alias='customSmtp') + emailbranding: bool = Field(..., alias='emailBranding') + requirespaymentmethod: bool = Field(..., alias='requiresPaymentMethod') + requiresbillingaddress: bool = Field(..., alias='requiresBillingAddress') + isavailable: bool = Field(..., alias='isAvailable') + selfservice: bool = Field(..., alias='selfService') + premiumsupport: bool = Field(..., alias='premiumSupport') + budgeting: bool = Field(..., alias='budgeting') + supportsmocknumbers: bool = Field(..., alias='supportsMockNumbers') + supportsorganizationroles: bool = Field(..., alias='supportsOrganizationRoles') + supportscredits: bool = Field(..., alias='supportsCredits') + supportsdisposableemailvalidation: bool = Field(..., alias='supportsDisposableEmailValidation') + supportscanonicalemailvalidation: bool = Field(..., alias='supportsCanonicalEmailValidation') + supportsfreeemailvalidation: bool = Field(..., alias='supportsFreeEmailValidation') + supportscorporateemailvalidation: bool = Field(..., alias='supportsCorporateEmailValidation') + supportsprojectspecificroles: bool = Field(..., alias='supportsProjectSpecificRoles') + backupsenabled: bool = Field(..., alias='backupsEnabled') + usageperproject: bool = Field(..., alias='usagePerProject') + supportedaddons: BillingPlanSupportedAddons = Field(..., alias='supportedAddons') + backuppolicies: float = Field(..., alias='backupPolicies') + deploymentsize: float = Field(..., alias='deploymentSize') + buildsize: float = Field(..., alias='buildSize') + databasesallowencrypt: bool = Field(..., alias='databasesAllowEncrypt') + limits: Optional[BillingPlanLimits] = Field(default=None, alias='limits') + group: BillingPlanGroup = Field(..., alias='group') + program: Optional[Program] = Field(default=None, alias='program') + dedicateddatabases: Optional[BillingPlanDedicatedDatabaseLimits] = Field(default=None, alias='dedicatedDatabases') diff --git a/appwrite/models/billing_plan_addon.py b/appwrite/models/billing_plan_addon.py new file mode 100644 index 00000000..b1d75b34 --- /dev/null +++ b/appwrite/models/billing_plan_addon.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .billing_plan_addon_details import BillingPlanAddonDetails + +class BillingPlanAddon(AppwriteModel): + """ + Addon + + Attributes + ---------- + seats : BillingPlanAddonDetails + Addon seats + projects : BillingPlanAddonDetails + Addon projects + """ + seats: BillingPlanAddonDetails = Field(..., alias='seats') + projects: BillingPlanAddonDetails = Field(..., alias='projects') diff --git a/appwrite/models/billing_plan_addon_details.py b/appwrite/models/billing_plan_addon_details.py new file mode 100644 index 00000000..8acd360c --- /dev/null +++ b/appwrite/models/billing_plan_addon_details.py @@ -0,0 +1,36 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class BillingPlanAddonDetails(AppwriteModel): + """ + Details + + Attributes + ---------- + supported : bool + Is the addon supported in the plan? + planincluded : float + Addon plan included value + limit : float + Addon limit + type : str + Addon type + currency : str + Price currency + price : float + Price + value : float + Resource value + invoicedesc : str + Description on invoice + """ + supported: bool = Field(..., alias='supported') + planincluded: float = Field(..., alias='planIncluded') + limit: float = Field(..., alias='limit') + type: str = Field(..., alias='type') + currency: str = Field(..., alias='currency') + price: float = Field(..., alias='price') + value: float = Field(..., alias='value') + invoicedesc: str = Field(..., alias='invoiceDesc') diff --git a/appwrite/models/billing_plan_dedicated_database_limits.py b/appwrite/models/billing_plan_dedicated_database_limits.py new file mode 100644 index 00000000..754c1666 --- /dev/null +++ b/appwrite/models/billing_plan_dedicated_database_limits.py @@ -0,0 +1,69 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class BillingPlanDedicatedDatabaseLimits(AppwriteModel): + """ + dedicatedDatabaseLimits + + Attributes + ---------- + mincpu : Optional[float] + Minimum CPU allocation in millicores. + maxcpu : Optional[float] + Maximum CPU allocation in millicores. + minmemorymb : Optional[float] + Minimum memory allocation in megabytes. + maxmemorymb : Optional[float] + Maximum memory allocation in megabytes. + minstoragegb : Optional[float] + Minimum storage allocation in gigabytes. + maxstoragegb : Optional[float] + Maximum storage allocation in gigabytes. + maxreplicas : Optional[float] + Maximum number of high-availability replicas per dedicated database. + maxconnections : Optional[float] + Maximum number of client connections. + maxipallowlistsize : Optional[float] + Maximum number of entries allowed in the IP allowlist. + maxextensions : Optional[float] + Maximum number of database extensions that can be enabled. + maxbackupretentiondays : Optional[float] + Maximum number of days a backup can be retained. + maxpitrretentiondays : Optional[float] + Maximum number of days of point-in-time recovery data that can be retained. + maxsqlapimaxrows : Optional[float] + Maximum number of rows a single SQL API query can return. + maxsqlapimaxbytes : Optional[float] + Maximum response size in bytes for a single SQL API query. + maxsqlapitimeoutseconds : Optional[float] + Maximum execution time in seconds for a single SQL API query. + maxsqlapiallowedstatements : Optional[float] + Maximum number of SQL statement types that can be permitted through the SQL API. + allowedsqlstatements : Optional[List[Any]] + SQL statement types permitted through the SQL API. + allowedstorageclasses : Optional[List[Any]] + Storage classes available for dedicated databases. + allowedsyncmodes : Optional[List[Any]] + Replica synchronization modes available for dedicated databases. + """ + mincpu: Optional[float] = Field(default=None, alias='minCpu') + maxcpu: Optional[float] = Field(default=None, alias='maxCpu') + minmemorymb: Optional[float] = Field(default=None, alias='minMemoryMb') + maxmemorymb: Optional[float] = Field(default=None, alias='maxMemoryMb') + minstoragegb: Optional[float] = Field(default=None, alias='minStorageGb') + maxstoragegb: Optional[float] = Field(default=None, alias='maxStorageGb') + maxreplicas: Optional[float] = Field(default=None, alias='maxReplicas') + maxconnections: Optional[float] = Field(default=None, alias='maxConnections') + maxipallowlistsize: Optional[float] = Field(default=None, alias='maxIpAllowlistSize') + maxextensions: Optional[float] = Field(default=None, alias='maxExtensions') + maxbackupretentiondays: Optional[float] = Field(default=None, alias='maxBackupRetentionDays') + maxpitrretentiondays: Optional[float] = Field(default=None, alias='maxPitrRetentionDays') + maxsqlapimaxrows: Optional[float] = Field(default=None, alias='maxSqlApiMaxRows') + maxsqlapimaxbytes: Optional[float] = Field(default=None, alias='maxSqlApiMaxBytes') + maxsqlapitimeoutseconds: Optional[float] = Field(default=None, alias='maxSqlApiTimeoutSeconds') + maxsqlapiallowedstatements: Optional[float] = Field(default=None, alias='maxSqlApiAllowedStatements') + allowedsqlstatements: Optional[List[Any]] = Field(default=None, alias='allowedSqlStatements') + allowedstorageclasses: Optional[List[Any]] = Field(default=None, alias='allowedStorageClasses') + allowedsyncmodes: Optional[List[Any]] = Field(default=None, alias='allowedSyncModes') diff --git a/appwrite/models/billing_plan_limits.py b/appwrite/models/billing_plan_limits.py new file mode 100644 index 00000000..4a5abbd9 --- /dev/null +++ b/appwrite/models/billing_plan_limits.py @@ -0,0 +1,18 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class BillingPlanLimits(AppwriteModel): + """ + PlanLimits + + Attributes + ---------- + credits : Optional[float] + Credits limit per billing cycle + dailycredits : Optional[float] + Daily credits limit (if applicable) + """ + credits: Optional[float] = Field(default=None, alias='credits') + dailycredits: Optional[float] = Field(default=None, alias='dailyCredits') diff --git a/appwrite/models/billing_plan_supported_addons.py b/appwrite/models/billing_plan_supported_addons.py new file mode 100644 index 00000000..be64dde3 --- /dev/null +++ b/appwrite/models/billing_plan_supported_addons.py @@ -0,0 +1,21 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class BillingPlanSupportedAddons(AppwriteModel): + """ + BillingPlanSupportedAddons + + Attributes + ---------- + baa : bool + Whether the plan supports BAA (Business Associate Agreement) addon + premiumgeodb : bool + Whether the plan supports Premium Geo DB addon (project-level) + premiumgeodborg : bool + Whether the plan supports Premium Geo DB addon (organization-level) + """ + baa: bool = Field(..., alias='baa') + premiumgeodb: bool = Field(..., alias='premiumGeoDB') + premiumgeodborg: bool = Field(..., alias='premiumGeoDBOrg') diff --git a/appwrite/models/block.py b/appwrite/models/block.py index 4e11336d..95ba2075 100644 --- a/appwrite/models/block.py +++ b/appwrite/models/block.py @@ -15,6 +15,8 @@ class Block(AppwriteModel): Resource type that is blocked resourceid : str Resource identifier that is blocked + mode : str + Block mode. full blocks reads and writes; readOnly blocks writes only. reason : Optional[str] Reason for the block. Can be null if no reason was provided. expiredat : Optional[str] @@ -33,6 +35,7 @@ class Block(AppwriteModel): createdat: str = Field(..., alias='$createdAt') resourcetype: str = Field(..., alias='resourceType') resourceid: str = Field(..., alias='resourceId') + mode: str = Field(..., alias='mode') reason: Optional[str] = Field(default=None, alias='reason') expiredat: Optional[str] = Field(default=None, alias='expiredAt') projectname: str = Field(..., alias='projectName') diff --git a/appwrite/models/database.py b/appwrite/models/database.py index 784fc2a7..b491a89a 100644 --- a/appwrite/models/database.py +++ b/appwrite/models/database.py @@ -3,6 +3,7 @@ from .base_model import AppwriteModel from ..enums.database_type import DatabaseType +from ..enums.database_status import DatabaseStatus from .backup_policy import BackupPolicy from .backup_archive import BackupArchive @@ -24,6 +25,8 @@ class Database(AppwriteModel): If database is enabled. Can be 'enabled' or 'disabled'. When disabled, the database is inaccessible to users, but remains accessible to Server SDKs using API keys. type : DatabaseType Database type. + status : Optional[DatabaseStatus] + Database status. Possible values: `provisioning`, `ready` or `failed` policies : List[BackupPolicy] Database backup policies. archives : List[BackupArchive] @@ -35,5 +38,6 @@ class Database(AppwriteModel): updatedat: str = Field(..., alias='$updatedAt') enabled: bool = Field(..., alias='enabled') type: DatabaseType = Field(..., alias='type') + status: Optional[DatabaseStatus] = Field(default=None, alias='status') policies: List[BackupPolicy] = Field(..., alias='policies') archives: List[BackupArchive] = Field(..., alias='archives') diff --git a/appwrite/models/document.py b/appwrite/models/document.py index dcff339a..4df880fc 100644 --- a/appwrite/models/document.py +++ b/appwrite/models/document.py @@ -57,6 +57,17 @@ def data(self) -> T: def data(self, value: T) -> None: object.__setattr__(self, '_data', value) + def model_dump(self, **kwargs) -> Dict[str, Any]: + result = super().model_dump(**kwargs) + if hasattr(self, '_data'): + if isinstance(self._data, dict): + result['data'] = self._data + elif hasattr(self._data, 'model_dump'): + result['data'] = self._data.model_dump(**kwargs) + else: + result['data'] = self._data + return result + def to_dict(self) -> Dict[str, Any]: result = super().to_dict() if hasattr(self, '_data'): diff --git a/appwrite/models/health_antivirus.py b/appwrite/models/health_antivirus.py deleted file mode 100644 index 4e15043d..00000000 --- a/appwrite/models/health_antivirus.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Any, Dict, List, Optional, Union, cast -from pydantic import Field, PrivateAttr - -from .base_model import AppwriteModel -from ..enums.health_antivirus_status import HealthAntivirusStatus - -class HealthAntivirus(AppwriteModel): - """ - Health Antivirus - - Attributes - ---------- - version : str - Antivirus version. - status : HealthAntivirusStatus - Antivirus status. Possible values are: `disabled`, `offline`, `online` - """ - version: str = Field(..., alias='version') - status: HealthAntivirusStatus = Field(..., alias='status') diff --git a/appwrite/models/health_certificate.py b/appwrite/models/health_certificate.py deleted file mode 100644 index 58999e72..00000000 --- a/appwrite/models/health_certificate.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Any, Dict, List, Optional, Union, cast -from pydantic import Field, PrivateAttr - -from .base_model import AppwriteModel - -class HealthCertificate(AppwriteModel): - """ - Health Certificate - - Attributes - ---------- - name : str - Certificate name - subjectsn : str - Subject SN - issuerorganisation : str - Issuer organisation - validfrom : str - Valid from - validto : str - Valid to - signaturetypesn : str - Signature type SN - """ - name: str = Field(..., alias='name') - subjectsn: str = Field(..., alias='subjectSN') - issuerorganisation: str = Field(..., alias='issuerOrganisation') - validfrom: str = Field(..., alias='validFrom') - validto: str = Field(..., alias='validTo') - signaturetypesn: str = Field(..., alias='signatureTypeSN') diff --git a/appwrite/models/health_status.py b/appwrite/models/health_status.py deleted file mode 100644 index 0ae11466..00000000 --- a/appwrite/models/health_status.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Any, Dict, List, Optional, Union, cast -from pydantic import Field, PrivateAttr - -from .base_model import AppwriteModel -from ..enums.health_check_status import HealthCheckStatus - -class HealthStatus(AppwriteModel): - """ - Health Status - - Attributes - ---------- - name : str - Name of the service. - ping : float - Duration in milliseconds how long the health check took. - status : HealthCheckStatus - Service status. Possible values are: `pass`, `fail` - """ - name: str = Field(..., alias='name') - ping: float = Field(..., alias='ping') - status: HealthCheckStatus = Field(..., alias='status') diff --git a/appwrite/models/health_status_list.py b/appwrite/models/health_status_list.py deleted file mode 100644 index 26904371..00000000 --- a/appwrite/models/health_status_list.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Any, Dict, List, Optional, Union, cast -from pydantic import Field, PrivateAttr - -from .base_model import AppwriteModel -from .health_status import HealthStatus - -class HealthStatusList(AppwriteModel): - """ - Status List - - Attributes - ---------- - total : float - Total number of statuses that matched your query. - statuses : List[HealthStatus] - List of statuses. - """ - total: float = Field(..., alias='total') - statuses: List[HealthStatus] = Field(..., alias='statuses') diff --git a/appwrite/models/health_time.py b/appwrite/models/health_time.py deleted file mode 100644 index 240694d9..00000000 --- a/appwrite/models/health_time.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Any, Dict, List, Optional, Union, cast -from pydantic import Field, PrivateAttr - -from .base_model import AppwriteModel - -class HealthTime(AppwriteModel): - """ - Health Time - - Attributes - ---------- - remotetime : float - Current unix timestamp on trustful remote server. - localtime : float - Current unix timestamp of local server where Appwrite runs. - diff : float - Difference of unix remote and local timestamps in milliseconds. - """ - remotetime: float = Field(..., alias='remoteTime') - localtime: float = Field(..., alias='localTime') - diff: float = Field(..., alias='diff') diff --git a/appwrite/models/locale.py b/appwrite/models/locale.py index 95952e14..f8aa6de8 100644 --- a/appwrite/models/locale.py +++ b/appwrite/models/locale.py @@ -23,6 +23,28 @@ class Locale(AppwriteModel): True if country is part of the European Union. currency : str Currency code in [ISO 4217-1](http://en.wikipedia.org/wiki/ISO_4217) three-character format + city : Optional[str] + City + timezone : Optional[str] + Name of timezone + postalcode : Optional[str] + Postal code + latitude : Optional[float] + Latitude + longitude : Optional[float] + Longitude + autonomoussystemnumber : Optional[str] + Autonomous System Number (ASN) of the IP + autonomoussystemorganization : Optional[str] + Organization that owns the ASN + isp : Optional[str] + Internet service provider of the IP + connectiontype : Optional[str] + Connection type of the IP (e.g. cable, cellular, corporate) + connectionusagetype : Optional[str] + User type classification of the IP (e.g. residential, business, hosting) + connectionorganization : Optional[str] + Registered organization of the IP """ ip: str = Field(..., alias='ip') countrycode: str = Field(..., alias='countryCode') @@ -31,3 +53,14 @@ class Locale(AppwriteModel): continent: str = Field(..., alias='continent') eu: bool = Field(..., alias='eu') currency: str = Field(..., alias='currency') + city: Optional[str] = Field(default=None, alias='city') + timezone: Optional[str] = Field(default=None, alias='timeZone') + postalcode: Optional[str] = Field(default=None, alias='postalCode') + latitude: Optional[float] = Field(default=None, alias='latitude') + longitude: Optional[float] = Field(default=None, alias='longitude') + autonomoussystemnumber: Optional[str] = Field(default=None, alias='autonomousSystemNumber') + autonomoussystemorganization: Optional[str] = Field(default=None, alias='autonomousSystemOrganization') + isp: Optional[str] = Field(default=None, alias='isp') + connectiontype: Optional[str] = Field(default=None, alias='connectionType') + connectionusagetype: Optional[str] = Field(default=None, alias='connectionUsageType') + connectionorganization: Optional[str] = Field(default=None, alias='connectionOrganization') diff --git a/appwrite/models/log.py b/appwrite/models/log.py index dc0e0c67..6824028e 100644 --- a/appwrite/models/log.py +++ b/appwrite/models/log.py @@ -20,7 +20,7 @@ class Log(AppwriteModel): mode : str API mode when event triggered. usertype : str - User type who triggered the audit log. Possible values: user, admin, guest, keyProject, keyAccount, keyOrganization. + User type who triggered the audit log. Possible values: user, admin, guest, hidden, keyProject, keyAccount, keyOrganization. ip : str IP session in use when the session was created. time : str diff --git a/appwrite/models/o_auth2_appwrite.py b/appwrite/models/o_auth2_appwrite.py new file mode 100644 index 00000000..002a8408 --- /dev/null +++ b/appwrite/models/o_auth2_appwrite.py @@ -0,0 +1,24 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class OAuth2Appwrite(AppwriteModel): + """ + OAuth2Appwrite + + Attributes + ---------- + id : str + OAuth2 provider ID. + enabled : bool + OAuth2 provider is active and can be used to create sessions. + clientid : str + Appwrite OAuth2 client ID. + clientsecret : str + Appwrite OAuth2 client secret. + """ + id: str = Field(..., alias='$id') + enabled: bool = Field(..., alias='enabled') + clientid: str = Field(..., alias='clientId') + clientsecret: str = Field(..., alias='clientSecret') diff --git a/appwrite/models/o_auth2_oidc.py b/appwrite/models/o_auth2_oidc.py index 6b896196..0ad06fef 100644 --- a/appwrite/models/o_auth2_oidc.py +++ b/appwrite/models/o_auth2_oidc.py @@ -2,6 +2,7 @@ from pydantic import Field, PrivateAttr from .base_model import AppwriteModel +from ..enums.o_auth2_oidc_prompt import OAuth2OidcPrompt class OAuth2Oidc(AppwriteModel): """ @@ -25,6 +26,10 @@ class OAuth2Oidc(AppwriteModel): OpenID Connect token endpoint URL. userinfourl : str OpenID Connect user info endpoint URL. + prompt : List[OAuth2OidcPrompt] + OpenID Connect prompt values controlling the authentication and consent screens. + maxage : Optional[float] + Maximum authentication age in seconds. When set, the user must have authenticated within this many seconds. """ id: str = Field(..., alias='$id') enabled: bool = Field(..., alias='enabled') @@ -34,3 +39,5 @@ class OAuth2Oidc(AppwriteModel): authorizationurl: str = Field(..., alias='authorizationURL') tokenurl: str = Field(..., alias='tokenURL') userinfourl: str = Field(..., alias='userInfoURL') + prompt: List[OAuth2OidcPrompt] = Field(..., alias='prompt') + maxage: Optional[float] = Field(default=None, alias='maxAge') diff --git a/appwrite/models/o_auth2_provider_list.py b/appwrite/models/o_auth2_provider_list.py index b07ce754..e13f865a 100644 --- a/appwrite/models/o_auth2_provider_list.py +++ b/appwrite/models/o_auth2_provider_list.py @@ -33,6 +33,7 @@ from .o_auth2_tradeshift import OAuth2Tradeshift from .o_auth2_paypal import OAuth2Paypal from .o_auth2_gitlab import OAuth2Gitlab +from .o_auth2_appwrite import OAuth2Appwrite from .o_auth2_authentik import OAuth2Authentik from .o_auth2_auth0 import OAuth2Auth0 from .o_auth2_fusion_auth import OAuth2FusionAuth @@ -51,8 +52,8 @@ 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, OAuth2Authentik, OAuth2Auth0, OAuth2FusionAuth, OAuth2Keycloak, OAuth2Oidc, OAuth2Apple, OAuth2Okta, OAuth2Kick, OAuth2Microsoft]] + 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]] List of OAuth2 providers. """ total: float = Field(..., alias='total') - 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, OAuth2Authentik, OAuth2Auth0, OAuth2FusionAuth, OAuth2Keycloak, OAuth2Oidc, OAuth2Apple, OAuth2Okta, OAuth2Kick, OAuth2Microsoft]] = Field(..., alias='providers') + 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]] = Field(..., alias='providers') diff --git a/appwrite/models/oauth2_grant.py b/appwrite/models/oauth2_grant.py index a17a74fd..58676082 100644 --- a/appwrite/models/oauth2_grant.py +++ b/appwrite/models/oauth2_grant.py @@ -21,6 +21,8 @@ class Oauth2Grant(AppwriteModel): ID of the OAuth2 client (app) the grant was requested for. scopes : List[Any] Requested OAuth2 scopes the user is being asked to consent to. + resources : List[Any] + Requested RFC 8707 resource indicators the user is being asked to consent to. authorizationdetails : str Requested authorization_details the user is being asked to consent to, as a JSON string. Each entry has a `type` plus project-defined fields. prompt : str @@ -38,6 +40,7 @@ class Oauth2Grant(AppwriteModel): userid: str = Field(..., alias='userId') appid: str = Field(..., alias='appId') scopes: List[Any] = Field(..., alias='scopes') + resources: List[Any] = Field(..., alias='resources') authorizationdetails: str = Field(..., alias='authorizationDetails') prompt: str = Field(..., alias='prompt') redirecturi: str = Field(..., alias='redirectUri') diff --git a/appwrite/models/health_queue.py b/appwrite/models/oauth2_organization.py similarity index 55% rename from appwrite/models/health_queue.py rename to appwrite/models/oauth2_organization.py index f9dcf66f..4481c4b2 100644 --- a/appwrite/models/health_queue.py +++ b/appwrite/models/oauth2_organization.py @@ -3,13 +3,13 @@ from .base_model import AppwriteModel -class HealthQueue(AppwriteModel): +class Oauth2Organization(AppwriteModel): """ - Health Queue + OAuth2 Organization Attributes ---------- - size : float - Amount of actions in the queue. + id : str + Organization ID. """ - size: float = Field(..., alias='size') + id: str = Field(..., alias='$id') diff --git a/appwrite/models/oauth2_organization_list.py b/appwrite/models/oauth2_organization_list.py new file mode 100644 index 00000000..aee823a0 --- /dev/null +++ b/appwrite/models/oauth2_organization_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .oauth2_organization import Oauth2Organization + +class Oauth2OrganizationList(AppwriteModel): + """ + OAuth2 accessible organizations list + + Attributes + ---------- + total : float + Total number of organizations that matched your query. + organizations : List[Oauth2Organization] + List of organizations. + """ + total: float = Field(..., alias='total') + organizations: List[Oauth2Organization] = Field(..., alias='organizations') diff --git a/appwrite/models/oauth2_par.py b/appwrite/models/oauth2_par.py new file mode 100644 index 00000000..a5942c86 --- /dev/null +++ b/appwrite/models/oauth2_par.py @@ -0,0 +1,18 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class Oauth2PAR(AppwriteModel): + """ + OAuth2 PAR + + Attributes + ---------- + request_uri : str + Authorization request handle to pass to the authorize endpoint. + expires_in : float + Lifetime of the authorization request handle in seconds. + """ + request_uri: str = Field(..., alias='request_uri') + expires_in: float = Field(..., alias='expires_in') diff --git a/appwrite/models/oauth2_project.py b/appwrite/models/oauth2_project.py new file mode 100644 index 00000000..f7bac058 --- /dev/null +++ b/appwrite/models/oauth2_project.py @@ -0,0 +1,21 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class Oauth2Project(AppwriteModel): + """ + OAuth2 Project + + Attributes + ---------- + id : str + Project ID. + region : str + Region ID the project is deployed in. + endpoint : str + API endpoint of the region the project is deployed in. Empty when the region has no public hostname configured. + """ + id: str = Field(..., alias='$id') + region: str = Field(..., alias='region') + endpoint: str = Field(..., alias='endpoint') diff --git a/appwrite/models/oauth2_project_list.py b/appwrite/models/oauth2_project_list.py new file mode 100644 index 00000000..e67b8929 --- /dev/null +++ b/appwrite/models/oauth2_project_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .oauth2_project import Oauth2Project + +class Oauth2ProjectList(AppwriteModel): + """ + OAuth2 accessible projects list + + Attributes + ---------- + total : float + Total number of projects that matched your query. + projects : List[Oauth2Project] + List of projects. + """ + total: float = Field(..., alias='total') + projects: List[Oauth2Project] = Field(..., alias='projects') diff --git a/appwrite/models/organization.py b/appwrite/models/organization.py new file mode 100644 index 00000000..95f4bdef --- /dev/null +++ b/appwrite/models/organization.py @@ -0,0 +1,132 @@ +from typing import Any, Dict, List, Optional, Union, cast, Generic, TypeVar, Type +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .preferences import Preferences +from .billing_plan import BillingPlan +from .billing_limits import BillingLimits + +T = TypeVar('T') + +class Organization(AppwriteModel, Generic[T]): + """ + Organization + + Attributes + ---------- + id : str + Team ID. + createdat : str + Team creation date in ISO 8601 format. + updatedat : str + Team update date in ISO 8601 format. + name : str + Team name. + total : float + Total number of team members. + prefs : Preferences[T] + Team preferences as a key-value object + billingbudget : float + Project budget limit + budgetalerts : List[Any] + Project budget limit + billingplan : str + Organization's billing plan ID. + billingplanid : str + Organization's billing plan ID. + billingplandetails : BillingPlan + Organization's billing plan. + billingemail : str + Billing email set for the organization. + billingstartdate : str + Billing cycle start date. + billingcurrentinvoicedate : str + Current invoice cycle start date. + billingnextinvoicedate : str + Next invoice cycle start date. + billingtrialstartdate : str + Start date of trial. + billingtrialdays : float + Number of trial days. + billingaggregationid : str + Current active aggregation id. + billinginvoiceid : str + Current active aggregation id. + paymentmethodid : str + Default payment method. + billingaddressid : str + Default payment method. + backuppaymentmethodid : str + Backup payment method. + status : str + Team status. + remarks : str + Remarks on team status. + agreementbaa : str + Organization agreements + programmanagername : str + Program manager's name. + programmanagercalendar : str + Program manager's calendar link. + programdiscordchannelname : str + Program's discord channel name. + programdiscordchannelurl : str + Program's discord channel URL. + billinglimits : Optional[BillingLimits] + Billing limits reached + billingplandowngrade : str + Billing plan selected for downgrade. + billingtaxid : str + Tax Id + markedfordeletion : bool + Marked for deletion + platform : str + Product with which the organization is associated (appwrite or imagine) + projects : List[Any] + Selected projects + """ + id: str = Field(..., alias='$id') + createdat: str = Field(..., alias='$createdAt') + updatedat: str = Field(..., alias='$updatedAt') + name: str = Field(..., alias='name') + total: float = Field(..., alias='total') + prefs: Preferences[T] = Field(..., alias='prefs') + billingbudget: float = Field(..., alias='billingBudget') + budgetalerts: List[Any] = Field(..., alias='budgetAlerts') + billingplan: str = Field(..., alias='billingPlan') + billingplanid: str = Field(..., alias='billingPlanId') + billingplandetails: BillingPlan = Field(..., alias='billingPlanDetails') + billingemail: str = Field(..., alias='billingEmail') + billingstartdate: str = Field(..., alias='billingStartDate') + billingcurrentinvoicedate: str = Field(..., alias='billingCurrentInvoiceDate') + billingnextinvoicedate: str = Field(..., alias='billingNextInvoiceDate') + billingtrialstartdate: str = Field(..., alias='billingTrialStartDate') + billingtrialdays: float = Field(..., alias='billingTrialDays') + billingaggregationid: str = Field(..., alias='billingAggregationId') + billinginvoiceid: str = Field(..., alias='billingInvoiceId') + paymentmethodid: str = Field(..., alias='paymentMethodId') + billingaddressid: str = Field(..., alias='billingAddressId') + backuppaymentmethodid: str = Field(..., alias='backupPaymentMethodId') + status: str = Field(..., alias='status') + remarks: str = Field(..., alias='remarks') + agreementbaa: str = Field(..., alias='agreementBAA') + programmanagername: str = Field(..., alias='programManagerName') + programmanagercalendar: str = Field(..., alias='programManagerCalendar') + programdiscordchannelname: str = Field(..., alias='programDiscordChannelName') + programdiscordchannelurl: str = Field(..., alias='programDiscordChannelUrl') + billinglimits: Optional[BillingLimits] = Field(default=None, alias='billingLimits') + billingplandowngrade: str = Field(..., alias='billingPlanDowngrade') + billingtaxid: str = Field(..., alias='billingTaxId') + markedfordeletion: bool = Field(..., alias='markedForDeletion') + platform: str = Field(..., alias='platform') + projects: List[Any] = Field(..., alias='projects') + + @classmethod + def with_data(cls, data: Dict[str, Any], model_type: Type[T] = dict) -> 'Organization[T]': + """Create Organization instance with typed data.""" + instance = cls.model_validate(data) + if 'prefs' in data and data['prefs'] is not None: + instance.prefs = Preferences.with_data( + data['prefs'], model_type + ) + return instance diff --git a/appwrite/models/preferences.py b/appwrite/models/preferences.py index ce469590..73a68a21 100644 --- a/appwrite/models/preferences.py +++ b/appwrite/models/preferences.py @@ -29,6 +29,17 @@ def data(self) -> T: def data(self, value: T) -> None: object.__setattr__(self, '_data', value) + def model_dump(self, **kwargs) -> Dict[str, Any]: + result = super().model_dump(**kwargs) + if hasattr(self, '_data'): + if isinstance(self._data, dict): + result['data'] = self._data + elif hasattr(self._data, 'model_dump'): + result['data'] = self._data.model_dump(**kwargs) + else: + result['data'] = self._data + return result + def to_dict(self) -> Dict[str, Any]: result = super().to_dict() if hasattr(self, '_data'): diff --git a/appwrite/models/program.py b/appwrite/models/program.py new file mode 100644 index 00000000..49f068e9 --- /dev/null +++ b/appwrite/models/program.py @@ -0,0 +1,39 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class Program(AppwriteModel): + """ + Program + + Attributes + ---------- + id : str + Program ID + title : str + Program title + description : str + Program description + tag : str + Program tag for highlighting on console + icon : str + Program icon for highlighting on console + url : str + URL for more information on this program + active : bool + Whether this program is active + external : bool + Whether this program is external + billingplanid : str + Billing plan ID that this is program is associated with. + """ + id: str = Field(..., alias='$id') + title: str = Field(..., alias='title') + description: str = Field(..., alias='description') + tag: str = Field(..., alias='tag') + icon: str = Field(..., alias='icon') + url: str = Field(..., alias='url') + active: bool = Field(..., alias='active') + external: bool = Field(..., alias='external') + billingplanid: str = Field(..., alias='billingPlanId') diff --git a/appwrite/models/project.py b/appwrite/models/project.py index afd4b5a5..38e41404 100644 --- a/appwrite/models/project.py +++ b/appwrite/models/project.py @@ -57,6 +57,8 @@ class Project(AppwriteModel): Labels for the project. status : str Project status + onboarding : Dict[str, Any] + Stage progress (completed or skipped) with timestamps and actor types, keyed by stage id. authmethods : List[ProjectAuthMethod] List of auth methods. services : List[ProjectService] @@ -75,6 +77,8 @@ class Project(AppwriteModel): OAuth2 server authorization URL oauth2serverscopes : Optional[List[Any]] OAuth2 server allowed scopes + oauth2serverdefaultscopes : Optional[List[Any]] + OAuth2 server scopes used when an authorization request omits the scope parameter oauth2serverauthorizationdetailstypes : Optional[List[Any]] OAuth2 server accepted RFC 9396 authorization_details types oauth2serveraccesstokenduration : Optional[float] @@ -119,6 +123,7 @@ class Project(AppwriteModel): pingedat: str = Field(..., alias='pingedAt') labels: List[Any] = Field(..., alias='labels') status: str = Field(..., alias='status') + onboarding: Dict[str, Any] = Field(..., alias='onboarding') authmethods: List[ProjectAuthMethod] = Field(..., alias='authMethods') services: List[ProjectService] = Field(..., alias='services') protocols: List[ProjectProtocol] = Field(..., alias='protocols') @@ -128,6 +133,7 @@ class Project(AppwriteModel): oauth2serverenabled: Optional[bool] = Field(default=None, alias='oAuth2ServerEnabled') oauth2serverauthorizationurl: Optional[str] = Field(default=None, alias='oAuth2ServerAuthorizationUrl') oauth2serverscopes: Optional[List[Any]] = Field(default=None, alias='oAuth2ServerScopes') + oauth2serverdefaultscopes: Optional[List[Any]] = Field(default=None, alias='oAuth2ServerDefaultScopes') oauth2serverauthorizationdetailstypes: Optional[List[Any]] = Field(default=None, alias='oAuth2ServerAuthorizationDetailsTypes') oauth2serveraccesstokenduration: Optional[float] = Field(default=None, alias='oAuth2ServerAccessTokenDuration') oauth2serverrefreshtokenduration: Optional[float] = Field(default=None, alias='oAuth2ServerRefreshTokenDuration') diff --git a/appwrite/models/row.py b/appwrite/models/row.py index 8ccc97e4..e6e15976 100644 --- a/appwrite/models/row.py +++ b/appwrite/models/row.py @@ -57,6 +57,17 @@ def data(self) -> T: def data(self, value: T) -> None: object.__setattr__(self, '_data', value) + def model_dump(self, **kwargs) -> Dict[str, Any]: + result = super().model_dump(**kwargs) + if hasattr(self, '_data'): + if isinstance(self._data, dict): + result['data'] = self._data + elif hasattr(self._data, 'model_dump'): + result['data'] = self._data.model_dump(**kwargs) + else: + result['data'] = self._data + return result + def to_dict(self) -> Dict[str, Any]: result = super().to_dict() if hasattr(self, '_data'): diff --git a/appwrite/models/usage_billing_plan.py b/appwrite/models/usage_billing_plan.py new file mode 100644 index 00000000..a078e726 --- /dev/null +++ b/appwrite/models/usage_billing_plan.py @@ -0,0 +1,46 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .additional_resource import AdditionalResource + +class UsageBillingPlan(AppwriteModel): + """ + usageBillingPlan + + Attributes + ---------- + bandwidth : AdditionalResource + Bandwidth additional resources + executions : AdditionalResource + Executions additional resources + member : AdditionalResource + Member additional resources + realtime : AdditionalResource + Realtime additional resources + realtimemessages : AdditionalResource + Realtime messages additional resources + realtimebandwidth : AdditionalResource + Realtime bandwidth additional resources + storage : AdditionalResource + Storage additional resources + users : AdditionalResource + User additional resources + gbhours : AdditionalResource + GBHour additional resources + imagetransformations : AdditionalResource + Image transformation additional resources + credits : AdditionalResource + Credits additional resources + """ + bandwidth: AdditionalResource = Field(..., alias='bandwidth') + executions: AdditionalResource = Field(..., alias='executions') + member: AdditionalResource = Field(..., alias='member') + realtime: AdditionalResource = Field(..., alias='realtime') + realtimemessages: AdditionalResource = Field(..., alias='realtimeMessages') + realtimebandwidth: AdditionalResource = Field(..., alias='realtimeBandwidth') + storage: AdditionalResource = Field(..., alias='storage') + users: AdditionalResource = Field(..., alias='users') + gbhours: AdditionalResource = Field(..., alias='GBHours') + imagetransformations: AdditionalResource = Field(..., alias='imageTransformations') + credits: AdditionalResource = Field(..., alias='credits') diff --git a/appwrite/models/usage_data_point.py b/appwrite/models/usage_data_point.py deleted file mode 100644 index a314956c..00000000 --- a/appwrite/models/usage_data_point.py +++ /dev/null @@ -1,60 +0,0 @@ -from typing import Any, Dict, List, Optional, Union, cast -from pydantic import Field, PrivateAttr - -from .base_model import AppwriteModel - -class UsageDataPoint(AppwriteModel): - """ - usageDataPoint - - Attributes - ---------- - time : str - Bucket start timestamp (ISO 8601). When `interval` is omitted this is the request end time, marking the aggregate as-of moment. - value : float - Aggregated value for the bucket. - path : Optional[str] - API endpoint path when broken down by `path`. - method : Optional[str] - HTTP method when broken down by `method`. - status : Optional[str] - HTTP status code when broken down by `status`. - service : Optional[str] - API service segment when broken down by `service`. - country : Optional[str] - Country code when broken down by `country`. - region : Optional[str] - Appwrite region when broken down by `region`. - hostname : Optional[str] - Caller origin hostname when broken down by `hostname`. - osname : Optional[str] - Operating system name when broken down by `osName`. - clienttype : Optional[str] - Client type when broken down by `clientType`. - clientname : Optional[str] - Client name when broken down by `clientName`. - devicename : Optional[str] - Device classification when broken down by `deviceName`. - teamid : Optional[str] - Owning team ID when broken down by `teamId`. - resourceid : Optional[str] - External resource ID when broken down by `resourceId`. - resource : Optional[str] - Resource type when broken down by `resource` (gauges only). - """ - time: str = Field(..., alias='time') - value: float = Field(..., alias='value') - path: Optional[str] = Field(default=None, alias='path') - method: Optional[str] = Field(default=None, alias='method') - status: Optional[str] = Field(default=None, alias='status') - service: Optional[str] = Field(default=None, alias='service') - country: Optional[str] = Field(default=None, alias='country') - region: Optional[str] = Field(default=None, alias='region') - hostname: Optional[str] = Field(default=None, alias='hostname') - osname: Optional[str] = Field(default=None, alias='osName') - clienttype: Optional[str] = Field(default=None, alias='clientType') - clientname: Optional[str] = Field(default=None, alias='clientName') - devicename: Optional[str] = Field(default=None, alias='deviceName') - teamid: Optional[str] = Field(default=None, alias='teamId') - resourceid: Optional[str] = Field(default=None, alias='resourceId') - resource: Optional[str] = Field(default=None, alias='resource') diff --git a/appwrite/models/usage_event_list.py b/appwrite/models/usage_event_list.py deleted file mode 100644 index c183c3e4..00000000 --- a/appwrite/models/usage_event_list.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Any, Dict, List, Optional, Union, cast -from pydantic import Field, PrivateAttr - -from .base_model import AppwriteModel -from .usage_metric import UsageMetric - -class UsageEventList(AppwriteModel): - """ - usageEventList - - Attributes - ---------- - interval : str - Time interval size (1h or 1d). Empty when the request omits `interval` — points then carry the request end time as their as-of marker. - metrics : List[UsageMetric] - One entry per requested metric, each carrying its own points[] time series (sums per bucket / dimension over time). - """ - interval: str = Field(..., alias='interval') - metrics: List[UsageMetric] = Field(..., alias='metrics') diff --git a/appwrite/models/usage_gauge_list.py b/appwrite/models/usage_gauge_list.py deleted file mode 100644 index ee3727ae..00000000 --- a/appwrite/models/usage_gauge_list.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Any, Dict, List, Optional, Union, cast -from pydantic import Field, PrivateAttr - -from .base_model import AppwriteModel -from .usage_metric import UsageMetric - -class UsageGaugeList(AppwriteModel): - """ - usageGaugeList - - Attributes - ---------- - interval : str - Time interval size (1h or 1d). Empty when the request omits `interval` — points then carry the request end time as their as-of marker. - metrics : List[UsageMetric] - One entry per requested metric, each carrying its own points[] time series (latest-snapshot per bucket / dimension via argMax over time). - """ - interval: str = Field(..., alias='interval') - metrics: List[UsageMetric] = Field(..., alias='metrics') diff --git a/appwrite/models/usage_metric.py b/appwrite/models/usage_metric.py deleted file mode 100644 index 3cd15d47..00000000 --- a/appwrite/models/usage_metric.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Any, Dict, List, Optional, Union, cast -from pydantic import Field, PrivateAttr - -from .base_model import AppwriteModel -from .usage_data_point import UsageDataPoint - -class UsageMetric(AppwriteModel): - """ - usageMetric - - Attributes - ---------- - metric : str - Metric key this series describes. - points : List[UsageDataPoint] - Data points for this metric, ordered by time ascending. With `interval`, each entry is one bucket; without, each entry is one row of the dimensional or aggregate breakdown. - """ - metric: str = Field(..., alias='metric') - points: List[UsageDataPoint] = Field(..., alias='points') diff --git a/appwrite/query.py b/appwrite/query.py index c223a4e1..3dafd4aa 100644 --- a/appwrite/query.py +++ b/appwrite/query.py @@ -248,6 +248,18 @@ def distance_greater_than(attribute, values, distance, meters=True): def distance_less_than(attribute, values, distance, meters=True): return str(Query("distanceLessThan", attribute, [[values, distance, meters]])) + @staticmethod + def vector_dot(attribute, vector): + return str(Query("vectorDot", attribute, [vector])) + + @staticmethod + def vector_cosine(attribute, vector): + return str(Query("vectorCosine", attribute, [vector])) + + @staticmethod + def vector_euclidean(attribute, vector): + return str(Query("vectorEuclidean", attribute, [vector])) + @staticmethod def intersects(attribute, values): return str(Query("intersects", attribute, [values])) diff --git a/appwrite/services/account.py b/appwrite/services/account.py index 21b734d1..5af533a9 100644 --- a/appwrite/services/account.py +++ b/appwrite/services/account.py @@ -1606,7 +1606,7 @@ def create_o_auth2_token( Parameters ---------- provider : OAuthProvider - OAuth2 Provider. Currently, supported providers are: amazon, apple, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, 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, dailymotion, discord, disqus, dropbox, etsy, facebook, figma, fusionauth, github, gitlab, google, keycloak, kick, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, 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] diff --git a/appwrite/services/apps.py b/appwrite/services/apps.py index da3c25d3..ab3e9110 100644 --- a/appwrite/services/apps.py +++ b/appwrite/services/apps.py @@ -396,6 +396,53 @@ def delete( return response + def update_labels( + self, + app_id: str, + labels: List[str] + ) -> App: + """ + Update the labels of an application. Labels are read-only for clients; only a server SDK using a project API key can set them. Replaces the previous labels. + + Parameters + ---------- + app_id : str + Application unique ID. + labels : List[str] + Array of application labels. Replaces the previous labels. Maximum of 1000 labels are allowed, each up to 36 alphanumeric characters long. + + Returns + ------- + App + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/labels' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + if labels is None: + raise AppwriteException('Missing required parameter: "labels"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + + api_params['labels'] = self._normalize_value(labels) + + response = self.client.call('put', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=App) + + def list_secrets( self, app_id: str, diff --git a/appwrite/services/backups.py b/appwrite/services/backups.py index a13743d6..fda14881 100644 --- a/appwrite/services/backups.py +++ b/appwrite/services/backups.py @@ -429,10 +429,14 @@ def create_restoration( archive_id: str, services: List[BackupServices], new_resource_id: Optional[str] = None, - new_resource_name: Optional[str] = None + new_resource_name: Optional[str] = None, + new_specification: Optional[str] = None ) -> BackupRestoration: """ Create and trigger a new restoration for a backup on a project. + + When restoring a DocumentsDB or VectorsDB database to a new resource, pass `newSpecification` to provision the restored database on a different specification than the archived one (for example, restoring onto a larger or smaller dedicated database). Use `serverless` to restore onto the shared pool, or a dedicated specification slug to restore onto a dedicated database of that size. The specification must be permitted by the organization's plan. `newSpecification` is not supported for legacy/TablesDB databases or for bucket restores. + Parameters ---------- @@ -444,6 +448,8 @@ def create_restoration( Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. new_resource_name : Optional[str] Database name. Max length: 128 chars. + new_specification : Optional[str] + Specification to provision the restored database on, when restoring a DocumentsDB or VectorsDB database to a new resource. Defaults to the archived database's specification. Use `serverless` for the shared pool or a dedicated specification slug. Returns ------- @@ -471,6 +477,8 @@ def create_restoration( api_params['newResourceId'] = self._normalize_value(new_resource_id) if new_resource_name is not None: api_params['newResourceName'] = self._normalize_value(new_resource_name) + if new_specification is not None: + api_params['newSpecification'] = self._normalize_value(new_specification) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/functions.py b/appwrite/services/functions.py index 7d9a0f14..18709fb2 100644 --- a/appwrite/services/functions.py +++ b/appwrite/services/functions.py @@ -126,7 +126,7 @@ def create( commands : Optional[str] Build Commands. scopes : Optional[List[ProjectKeyScopes]] - List of scopes allowed for API key auto-generated for every execution. Maximum of 100 scopes are allowed. + List of scopes allowed for API key auto-generated for every execution. Maximum of 200 scopes are allowed. installation_id : Optional[str] Appwrite Installation ID for VCS (Version Control System) deployment. provider_repository_id : Optional[str] @@ -251,11 +251,17 @@ def list_runtimes( def list_specifications( - self + self, + type: Optional[str] = None ) -> SpecificationList: """ List allowed function specifications for this instance. + Parameters + ---------- + type : Optional[str] + Specification type to list. Can be one of: runtimes, builds. + Returns ------- SpecificationList @@ -270,6 +276,9 @@ def list_specifications( api_path = '/functions/specifications' api_params = {} + if type is not None: + api_params['type'] = self._normalize_value(type) + response = self.client.call('get', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), 'accept': 'application/json', @@ -370,7 +379,7 @@ def update( commands : Optional[str] Build Commands. scopes : Optional[List[ProjectKeyScopes]] - List of scopes allowed for API Key auto-generated for every execution. Maximum of 100 scopes are allowed. + List of scopes allowed for API Key auto-generated for every execution. Maximum of 200 scopes are allowed. installation_id : Optional[str] Appwrite Installation ID for VCS (Version Controle System) deployment. provider_repository_id : Optional[str] @@ -445,8 +454,7 @@ def update( api_params['providerRootDirectory'] = self._normalize_value(provider_root_directory) api_params['providerBranches'] = self._normalize_value(provider_branches) api_params['providerPaths'] = self._normalize_value(provider_paths) - if build_specification is not None: - api_params['buildSpecification'] = self._normalize_value(build_specification) + api_params['buildSpecification'] = self._normalize_value(build_specification) if runtime_specification is not None: api_params['runtimeSpecification'] = self._normalize_value(runtime_specification) if deployment_retention is not None: @@ -968,7 +976,8 @@ def get_deployment_download( self, function_id: str, deployment_id: str, - type: Optional[DeploymentDownloadType] = None + type: Optional[DeploymentDownloadType] = None, + token: Optional[str] = None ) -> bytes: """ Get a function deployment content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory. @@ -981,6 +990,8 @@ def get_deployment_download( Deployment ID. type : Optional[DeploymentDownloadType] Deployment file to download. Can be: "source", "output". + token : Optional[str] + Presigned source-download token for accessing this deployment without a session (jobs-service). Returns ------- @@ -1006,6 +1017,8 @@ def get_deployment_download( if type is not None: api_params['type'] = self._normalize_value(type) + if token is not None: + api_params['token'] = self._normalize_value(token) response = self.client.call('get', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/health.py b/appwrite/services/health.py deleted file mode 100644 index bbf93903..00000000 --- a/appwrite/services/health.py +++ /dev/null @@ -1,882 +0,0 @@ -from ..service import Service -from urllib.parse import quote -from typing import Any, Dict, List, Optional, Union -from ..exception import AppwriteException -from appwrite.utils.deprecated import deprecated -from ..models.health_status import HealthStatus -from ..models.health_antivirus import HealthAntivirus -from ..models.health_status_list import HealthStatusList -from ..models.health_certificate import HealthCertificate -from ..models.health_queue import HealthQueue -from ..enums.health_queue_name import HealthQueueName -from ..models.health_time import HealthTime - -class Health(Service): - - def __init__(self, client) -> None: - super(Health, self).__init__(client) - - def get( - self - ) -> HealthStatus: - """ - Check the Appwrite HTTP server is up and responsive. - - Returns - ------- - HealthStatus - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health' - api_params = {} - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthStatus) - - - def get_antivirus( - self - ) -> HealthAntivirus: - """ - Check the Appwrite Antivirus server is up and connection is successful. - - Returns - ------- - HealthAntivirus - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/anti-virus' - api_params = {} - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthAntivirus) - - - def get_audits_db( - self - ) -> HealthStatusList: - """ - Check the database that backs the audit and activity store. When the connection is reachable the endpoint returns a passing status with its response time. - - - Returns - ------- - HealthStatusList - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/audits-db' - api_params = {} - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthStatusList) - - - def get_cache( - self - ) -> HealthStatusList: - """ - Check the Appwrite in-memory cache servers are up and connection is successful. - - Returns - ------- - HealthStatusList - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/cache' - api_params = {} - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthStatusList) - - - def get_certificate( - self, - domain: Optional[str] = None - ) -> HealthCertificate: - """ - Get the SSL certificate for a domain - - Parameters - ---------- - domain : Optional[str] - string - - Returns - ------- - HealthCertificate - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/certificate' - api_params = {} - - if domain is not None: - api_params['domain'] = self._normalize_value(domain) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthCertificate) - - - def get_console_pausing( - self, - threshold: Optional[float] = None, - inactivity_days: Optional[float] = None - ) -> HealthStatus: - """ - Get console pausing health status. Monitors projects approaching the pause threshold to detect potential issues with console access tracking. - - - Parameters - ---------- - threshold : Optional[float] - Percentage threshold of projects approaching pause. When hit (equal or higher), endpoint returns server error. Default value is 10. - inactivity_days : Optional[float] - Number of days of inactivity before a project is paused. Should match the plan's projectInactivityDays setting. Default value is 7. - - Returns - ------- - HealthStatus - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/console-pausing' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - if inactivity_days is not None: - api_params['inactivityDays'] = self._normalize_value(inactivity_days) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthStatus) - - - def get_db( - self - ) -> HealthStatusList: - """ - Check the Appwrite database servers are up and connection is successful. - - Returns - ------- - HealthStatusList - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/db' - api_params = {} - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthStatusList) - - - def get_pub_sub( - self - ) -> HealthStatusList: - """ - Check the Appwrite pub-sub servers are up and connection is successful. - - Returns - ------- - HealthStatusList - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/pubsub' - api_params = {} - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthStatusList) - - - def get_queue_audits( - self, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of audit logs that are waiting to be processed in the Appwrite internal queue server. - - - Parameters - ---------- - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/audits' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_queue_builds( - self, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of builds that are waiting to be processed in the Appwrite internal queue server. - - Parameters - ---------- - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/builds' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_queue_certificates( - self, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of certificates that are waiting to be issued against [Letsencrypt](https://letsencrypt.org/) in the Appwrite internal queue server. - - Parameters - ---------- - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/certificates' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_queue_databases( - self, - name: Optional[str] = None, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of database changes that are waiting to be processed in the Appwrite internal queue server. - - Parameters - ---------- - name : Optional[str] - Queue name for which to check the queue size - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/databases' - api_params = {} - - if name is not None: - api_params['name'] = self._normalize_value(name) - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_queue_deletes( - self, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of background destructive changes that are waiting to be processed in the Appwrite internal queue server. - - Parameters - ---------- - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/deletes' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_failed_jobs( - self, - name: HealthQueueName, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Returns the amount of failed jobs in a given queue. - - - Parameters - ---------- - name : HealthQueueName - The name of the queue - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/failed/{name}' - api_params = {} - if name is None: - raise AppwriteException('Missing required parameter: "name"') - - api_path = api_path.replace('{name}', str(self._normalize_value(name))) - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_queue_functions( - self, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of function executions that are waiting to be processed in the Appwrite internal queue server. - - Parameters - ---------- - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/functions' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_queue_logs( - self, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of logs that are waiting to be processed in the Appwrite internal queue server. - - Parameters - ---------- - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/logs' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_queue_mails( - self, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of mails that are waiting to be processed in the Appwrite internal queue server. - - Parameters - ---------- - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/mails' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_queue_messaging( - self, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of messages that are waiting to be processed in the Appwrite internal queue server. - - Parameters - ---------- - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/messaging' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_queue_migrations( - self, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of migrations that are waiting to be processed in the Appwrite internal queue server. - - Parameters - ---------- - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/migrations' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_queue_stats_resources( - self, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of metrics that are waiting to be processed in the Appwrite stats resources queue. - - Parameters - ---------- - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/stats-resources' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_queue_usage( - self, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of metrics that are waiting to be processed in the Appwrite internal queue server. - - Parameters - ---------- - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/stats-usage' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_queue_webhooks( - self, - threshold: Optional[float] = None - ) -> HealthQueue: - """ - Get the number of webhooks that are waiting to be processed in the Appwrite internal queue server. - - Parameters - ---------- - threshold : Optional[float] - Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000. - - Returns - ------- - HealthQueue - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/queue/webhooks' - api_params = {} - - if threshold is not None: - api_params['threshold'] = self._normalize_value(threshold) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthQueue) - - - def get_storage( - self - ) -> HealthStatus: - """ - Check the Appwrite storage device is up and connection is successful. - - Returns - ------- - HealthStatus - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/storage' - api_params = {} - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthStatus) - - - def get_storage_local( - self - ) -> HealthStatus: - """ - Check the Appwrite local storage device is up and connection is successful. - - Returns - ------- - HealthStatus - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/storage/local' - api_params = {} - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthStatus) - - - def get_time( - self - ) -> HealthTime: - """ - Check the Appwrite server time is synced with Google remote NTP server. We use this technology to smoothly handle leap seconds with no disruptive events. The [Network Time Protocol](https://en.wikipedia.org/wiki/Network_Time_Protocol) (NTP) is used by hundreds of millions of computers and devices to synchronize their clocks over the Internet. If your computer sets its own clock, it likely uses NTP. - - Returns - ------- - HealthTime - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/health/time' - api_params = {} - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=HealthTime) - diff --git a/appwrite/services/messaging.py b/appwrite/services/messaging.py index 065a9537..3abb1004 100644 --- a/appwrite/services/messaging.py +++ b/appwrite/services/messaging.py @@ -6,7 +6,6 @@ from ..models.message_list import MessageList from ..models.message import Message from ..enums.message_priority import MessagePriority -from ..models.log_list import LogList from ..models.target_list import TargetList from ..models.provider_list import ProviderList from ..models.provider import Provider @@ -707,55 +706,6 @@ def delete( return response - def list_message_logs( - self, - message_id: str, - queries: Optional[List[str]] = None, - total: Optional[bool] = None - ) -> LogList: - """ - Get the message activity logs listed by its unique ID. - - Parameters - ---------- - message_id : str - Message ID. - queries : Optional[List[str]] - Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset - total : Optional[bool] - When set to false, the total count returned will be 0 and will not be calculated. - - Returns - ------- - LogList - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/messaging/messages/{messageId}/logs' - api_params = {} - if message_id is None: - raise AppwriteException('Missing required parameter: "message_id"') - - api_path = api_path.replace('{messageId}', str(self._normalize_value(message_id))) - - if queries is not None: - api_params['queries'] = self._normalize_value(queries) - if total is not None: - api_params['total'] = self._normalize_value(total) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=LogList) - - def list_targets( self, message_id: str, @@ -2686,104 +2636,6 @@ def delete_provider( return response - def list_provider_logs( - self, - provider_id: str, - queries: Optional[List[str]] = None, - total: Optional[bool] = None - ) -> LogList: - """ - Get the provider activity logs listed by its unique ID. - - Parameters - ---------- - provider_id : str - Provider ID. - queries : Optional[List[str]] - Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset - total : Optional[bool] - When set to false, the total count returned will be 0 and will not be calculated. - - Returns - ------- - LogList - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/messaging/providers/{providerId}/logs' - api_params = {} - if provider_id is None: - raise AppwriteException('Missing required parameter: "provider_id"') - - api_path = api_path.replace('{providerId}', str(self._normalize_value(provider_id))) - - if queries is not None: - api_params['queries'] = self._normalize_value(queries) - if total is not None: - api_params['total'] = self._normalize_value(total) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=LogList) - - - def list_subscriber_logs( - self, - subscriber_id: str, - queries: Optional[List[str]] = None, - total: Optional[bool] = None - ) -> LogList: - """ - Get the subscriber activity logs listed by its unique ID. - - Parameters - ---------- - subscriber_id : str - Subscriber ID. - queries : Optional[List[str]] - Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset - total : Optional[bool] - When set to false, the total count returned will be 0 and will not be calculated. - - Returns - ------- - LogList - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/messaging/subscribers/{subscriberId}/logs' - api_params = {} - if subscriber_id is None: - raise AppwriteException('Missing required parameter: "subscriber_id"') - - api_path = api_path.replace('{subscriberId}', str(self._normalize_value(subscriber_id))) - - if queries is not None: - api_params['queries'] = self._normalize_value(queries) - if total is not None: - api_params['total'] = self._normalize_value(total) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=LogList) - - def list_topics( self, queries: Optional[List[str]] = None, @@ -3011,55 +2863,6 @@ def delete_topic( return response - def list_topic_logs( - self, - topic_id: str, - queries: Optional[List[str]] = None, - total: Optional[bool] = None - ) -> LogList: - """ - Get the topic activity logs listed by its unique ID. - - Parameters - ---------- - topic_id : str - Topic ID. - queries : Optional[List[str]] - Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset - total : Optional[bool] - When set to false, the total count returned will be 0 and will not be calculated. - - Returns - ------- - LogList - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/messaging/topics/{topicId}/logs' - api_params = {} - if topic_id is None: - raise AppwriteException('Missing required parameter: "topic_id"') - - api_path = api_path.replace('{topicId}', str(self._normalize_value(topic_id))) - - if queries is not None: - api_params['queries'] = self._normalize_value(queries) - if total is not None: - api_params['total'] = self._normalize_value(total) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=LogList) - - def list_subscribers( self, topic_id: str, diff --git a/appwrite/services/oauth2.py b/appwrite/services/oauth2.py index 581d4e4e..272fbc73 100644 --- a/appwrite/services/oauth2.py +++ b/appwrite/services/oauth2.py @@ -7,6 +7,9 @@ from ..models.oauth2_authorize import Oauth2Authorize from ..models.oauth2_device_authorization import Oauth2DeviceAuthorization from ..models.oauth2_grant import Oauth2Grant +from ..models.oauth2_organization_list import Oauth2OrganizationList +from ..models.oauth2_par import Oauth2PAR +from ..models.oauth2_project_list import Oauth2ProjectList from ..models.oauth2_reject import Oauth2Reject from ..models.oauth2_token import Oauth2Token @@ -18,7 +21,8 @@ def __init__(self, client) -> None: def approve( self, grant_id: str, - authorization_details: Optional[str] = None + authorization_details: Optional[str] = None, + scope: Optional[str] = None ) -> Oauth2Approve: """ Approve an OAuth2 grant after the user gives consent. Returns the `redirectUrl` the end user should be sent to. The consent screen may optionally pass enriched `authorization_details` to record the concrete resources the user selected. You can pass Accept header of `application/json` to receive a JSON response instead of a redirect. @@ -29,6 +33,8 @@ def approve( Grant ID made during authorization, provided to consent screen in URL search params. authorization_details : Optional[str] Enriched `authorization_details` the user consented to, replacing what the client requested. Each entry must use a `type` the project accepts. Optional; omit to keep the originally requested details. + scope : Optional[str] + Space-separated scopes the user consented to. Must be a subset of the scopes originally requested; identity scopes such as `openid` are always retained. Optional; omit to keep the originally requested scopes. Returns ------- @@ -51,6 +57,8 @@ def approve( api_params['grant_id'] = self._normalize_value(grant_id) if authorization_details is not None: api_params['authorization_details'] = self._normalize_value(authorization_details) + if scope is not None: + api_params['scope'] = self._normalize_value(scope) response = self.client.call('post', api_path, { 'content-type': 'application/json', @@ -62,31 +70,34 @@ def approve( def authorize( self, - client_id: str, - redirect_uri: str, - response_type: str, - scope: str, + client_id: Optional[str] = None, + redirect_uri: Optional[str] = None, + response_type: Optional[str] = None, + scope: Optional[str] = None, state: Optional[str] = None, nonce: Optional[str] = None, code_challenge: Optional[str] = None, code_challenge_method: Optional[str] = None, prompt: Optional[str] = None, max_age: Optional[float] = None, - authorization_details: Optional[str] = None + authorization_details: Optional[str] = None, + resource: Optional[str] = None, + audience: Optional[str] = None, + request_uri: Optional[str] = None ) -> Oauth2Authorize: """ Begin the OAuth2 authorization flow. When called without a session, the user is redirected to the consent screen without grant ID. When called with a session, the redirect URL includes param for grant ID. You can pass Accept header of `application/json` to receive a JSON response instead of a redirect. Parameters ---------- - client_id : str + client_id : Optional[str] OAuth2 client ID. - redirect_uri : str + redirect_uri : Optional[str] Redirect URI where visitor will be redirected after authorization, whether successful or not. - response_type : str + response_type : Optional[str] OAuth2 / OIDC response type. One of `code` (Authorization Code Flow), `id_token` (Implicit Flow, OIDC login only), or `code id_token` (Hybrid Flow). - scope : str - Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`. + scope : Optional[str] + Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`, `phone`. state : Optional[str] OAuth2 state. You receive this back in the redirect URI. nonce : Optional[str] @@ -101,6 +112,12 @@ def authorize( OIDC max_age paraleter for customization of consent screen. Maximum allowable elapsed time in seconds since the user last authenticated. If exceeded, re-authentication is required. authorization_details : Optional[str] Rich authorization request. JSON array of objects, each with a `type` and project-defined fields + resource : Optional[str] + RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + audience : Optional[str] + Compatibility alias for a single OAuth2 resource indicator URI. + request_uri : Optional[str] + OAuth2 authorization request handle returned by the pushed authorization request endpoint. Returns ------- @@ -115,24 +132,16 @@ def authorize( api_path = '/oauth2/{project_id}/authorize' api_params = {} - if client_id is None: - raise AppwriteException('Missing required parameter: "client_id"') - - if redirect_uri is None: - raise AppwriteException('Missing required parameter: "redirect_uri"') - - if response_type is None: - raise AppwriteException('Missing required parameter: "response_type"') - - if scope is None: - raise AppwriteException('Missing required parameter: "scope"') - api_path = api_path.replace('{project_id}', str(self._normalize_value(self.client.get_config('project')))) - api_params['client_id'] = self._normalize_value(client_id) - api_params['redirect_uri'] = self._normalize_value(redirect_uri) - api_params['response_type'] = self._normalize_value(response_type) - api_params['scope'] = self._normalize_value(scope) + if client_id is not None: + api_params['client_id'] = self._normalize_value(client_id) + if redirect_uri is not None: + api_params['redirect_uri'] = self._normalize_value(redirect_uri) + if response_type is not None: + api_params['response_type'] = self._normalize_value(response_type) + if scope is not None: + api_params['scope'] = self._normalize_value(scope) if state is not None: api_params['state'] = self._normalize_value(state) if nonce is not None: @@ -147,6 +156,12 @@ def authorize( api_params['max_age'] = self._normalize_value(max_age) if authorization_details is not None: api_params['authorization_details'] = self._normalize_value(authorization_details) + if resource is not None: + api_params['resource'] = self._normalize_value(resource) + if audience is not None: + api_params['audience'] = self._normalize_value(audience) + if request_uri is not None: + api_params['request_uri'] = self._normalize_value(request_uri) response = self.client.call('get', api_path, { 'accept': 'application/json', @@ -159,7 +174,9 @@ def create_device_authorization( self, client_id: Optional[str] = None, scope: Optional[str] = None, - authorization_details: Optional[str] = None + authorization_details: Optional[str] = None, + resource: Optional[str] = None, + audience: Optional[str] = None ) -> Oauth2DeviceAuthorization: """ Start the OAuth2 Device Authorization Grant. Returns the device code, user code, verification URL, expiration, and polling interval. @@ -172,6 +189,10 @@ def create_device_authorization( Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`. authorization_details : Optional[str] Rich authorization request. JSON array of objects, each with a `type` and project-defined fields + resource : Optional[str] + RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + audience : Optional[str] + Compatibility alias for a single OAuth2 resource indicator URI. Returns ------- @@ -194,6 +215,10 @@ def create_device_authorization( api_params['scope'] = self._normalize_value(scope) if authorization_details is not None: api_params['authorization_details'] = self._normalize_value(authorization_details) + if resource is not None: + api_params['resource'] = self._normalize_value(resource) + if audience is not None: + api_params['audience'] = self._normalize_value(audience) response = self.client.call('post', api_path, { 'content-type': 'application/json', @@ -282,6 +307,203 @@ def get_grant( return self._parse_response(response, model=Oauth2Grant) + def list_organizations( + self, + limit: Optional[float] = None, + offset: Optional[float] = None, + search: Optional[str] = None + ) -> Oauth2OrganizationList: + """ + List the organizations the OAuth2 access token can access. Resolves the token's `organization` authorization details, expanding the `*` wildcard into the concrete set of organizations the user can see. + + Parameters + ---------- + limit : Optional[float] + Maximum number of organizations to return. Between 1 and 5000. + offset : Optional[float] + Number of organizations to skip before returning results. Used for pagination. + search : Optional[str] + Search term to filter your list results. Max length: 256 chars. + + Returns + ------- + Oauth2OrganizationList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/oauth2/{project_id}/organizations' + api_params = {} + api_path = api_path.replace('{project_id}', str(self._normalize_value(self.client.get_config('project')))) + + if limit is not None: + api_params['limit'] = self._normalize_value(limit) + if offset is not None: + api_params['offset'] = self._normalize_value(offset) + if search is not None: + api_params['search'] = self._normalize_value(search) + + response = self.client.call('get', api_path, { + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Oauth2OrganizationList) + + + def create_par( + self, + client_id: str, + redirect_uri: str, + response_type: str, + scope: Optional[str] = None, + state: Optional[str] = None, + nonce: Optional[str] = None, + code_challenge: Optional[str] = None, + code_challenge_method: Optional[str] = None, + prompt: Optional[str] = None, + max_age: Optional[float] = None, + authorization_details: Optional[str] = None, + resource: Optional[str] = None, + audience: Optional[str] = None + ) -> Oauth2PAR: + """ + Store an OAuth2 authorization request server-side and receive a short-lived request_uri handle for the authorize endpoint. + + Parameters + ---------- + client_id : str + OAuth2 client ID. + redirect_uri : str + Redirect URI where visitor will be redirected after authorization, whether successful or not. + response_type : str + OAuth2 / OIDC response type. + scope : Optional[str] + Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`, `phone`. + state : Optional[str] + OAuth2 state. You receive this back in the redirect URI. + nonce : Optional[str] + OIDC nonce parameter to prevent replay attacks. Required when response_type includes `id_token`. + code_challenge : Optional[str] + PKCE code challenge. Required when OAuth2 app is public. + code_challenge_method : Optional[str] + PKCE code challenge method. Required when OAuth2 app is public. + prompt : Optional[str] + OIDC prompt parameter for customization of consent screen. Space-separated list of: none, login, consent, select_account. + max_age : Optional[float] + OIDC max_age parameter for customization of consent screen. + authorization_details : Optional[str] + Rich authorization request. JSON array of objects, each with a `type` and project-defined fields + resource : Optional[str] + RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + audience : Optional[str] + Compatibility alias for a single OAuth2 resource indicator URI. + + Returns + ------- + Oauth2PAR + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/oauth2/{project_id}/par' + api_params = {} + if client_id is None: + raise AppwriteException('Missing required parameter: "client_id"') + + if redirect_uri is None: + raise AppwriteException('Missing required parameter: "redirect_uri"') + + if response_type is None: + raise AppwriteException('Missing required parameter: "response_type"') + + api_path = api_path.replace('{project_id}', str(self._normalize_value(self.client.get_config('project')))) + + api_params['client_id'] = self._normalize_value(client_id) + api_params['redirect_uri'] = self._normalize_value(redirect_uri) + api_params['response_type'] = self._normalize_value(response_type) + if scope is not None: + api_params['scope'] = self._normalize_value(scope) + if state is not None: + api_params['state'] = self._normalize_value(state) + if nonce is not None: + api_params['nonce'] = self._normalize_value(nonce) + if code_challenge is not None: + api_params['code_challenge'] = self._normalize_value(code_challenge) + if code_challenge_method is not None: + api_params['code_challenge_method'] = self._normalize_value(code_challenge_method) + if prompt is not None: + api_params['prompt'] = self._normalize_value(prompt) + api_params['max_age'] = self._normalize_value(max_age) + if authorization_details is not None: + api_params['authorization_details'] = self._normalize_value(authorization_details) + if resource is not None: + api_params['resource'] = self._normalize_value(resource) + if audience is not None: + api_params['audience'] = self._normalize_value(audience) + + response = self.client.call('post', api_path, { + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Oauth2PAR) + + + def list_projects( + self, + limit: Optional[float] = None, + offset: Optional[float] = None, + search: Optional[str] = None + ) -> Oauth2ProjectList: + """ + List the projects the OAuth2 access token can access. Resolves the token's `project` authorization details, expanding the `*` wildcard into the concrete set of projects the user can see. + + Parameters + ---------- + limit : Optional[float] + Maximum number of projects to return. Between 1 and 5000. + offset : Optional[float] + Number of projects to skip before returning results. Used for pagination. + search : Optional[str] + Search term to filter your list results. Max length: 256 chars. + + Returns + ------- + Oauth2ProjectList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/oauth2/{project_id}/projects' + api_params = {} + api_path = api_path.replace('{project_id}', str(self._normalize_value(self.client.get_config('project')))) + + if limit is not None: + api_params['limit'] = self._normalize_value(limit) + if offset is not None: + api_params['offset'] = self._normalize_value(offset) + if search is not None: + api_params['search'] = self._normalize_value(search) + + response = self.client.call('get', api_path, { + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Oauth2ProjectList) + + def reject( self, grant_id: str @@ -386,7 +608,9 @@ def create_token( client_id: Optional[str] = None, client_secret: Optional[str] = None, code_verifier: Optional[str] = None, - redirect_uri: Optional[str] = None + redirect_uri: Optional[str] = None, + resource: Optional[str] = None, + audience: Optional[str] = None ) -> Oauth2Token: """ Exchange an OAuth2 authorization code, refresh token, or device code for access and refresh tokens. @@ -409,6 +633,10 @@ def create_token( PKCE code verifier. Required for public apps. redirect_uri : Optional[str] Redirect URI. Required for `authorization_code` grant type. + resource : Optional[str] + RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + audience : Optional[str] + Compatibility alias for a single OAuth2 resource indicator URI. Returns ------- @@ -443,6 +671,10 @@ def create_token( api_params['code_verifier'] = self._normalize_value(code_verifier) if redirect_uri is not None: api_params['redirect_uri'] = self._normalize_value(redirect_uri) + if resource is not None: + api_params['resource'] = self._normalize_value(resource) + if audience is not None: + api_params['audience'] = self._normalize_value(audience) response = self.client.call('post', api_path, { 'content-type': 'application/json', diff --git a/appwrite/services/organization.py b/appwrite/services/organization.py index 7c63003a..eaf0ca88 100644 --- a/appwrite/services/organization.py +++ b/appwrite/services/organization.py @@ -1,20 +1,132 @@ from ..service import Service from urllib.parse import quote -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union, Type, TypeVar from ..exception import AppwriteException from appwrite.utils.deprecated import deprecated +from ..models.organization import Organization as OrganizationModel from ..models.key_list import KeyList from ..enums.organization_key_scopes import OrganizationKeyScopes from ..models.key import Key +from ..models.membership_list import MembershipList +from ..models.membership import Membership from ..models.project_list import ProjectList from ..enums.region import Region from ..models.project import Project +T = TypeVar('T') + class Organization(Service): def __init__(self, client) -> None: super(Organization, self).__init__(client) + def get( + self, + model_type: Type[T] = dict + ) -> OrganizationModel[T]: + """ + Get the current organization. + + Parameters + ---------- + + model_type : Type[T], optional + Pydantic model class for the user-defined data. Defaults to dict for backward compatibility. + + Returns + ------- + OrganizationModel[T] + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization' + api_params = {} + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return OrganizationModel.with_data(response, model_type) + + + def update( + self, + name: str, + model_type: Type[T] = dict + ) -> OrganizationModel[T]: + """ + Update the current organization's name. + + Parameters + ---------- + name : str + New organization name. Max length: 128 chars. + + model_type : Type[T], optional + Pydantic model class for the user-defined data. Defaults to dict for backward compatibility. + + Returns + ------- + OrganizationModel[T] + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization' + api_params = {} + if name is None: + raise AppwriteException('Missing required parameter: "name"') + + + api_params['name'] = self._normalize_value(name) + + response = self.client.call('put', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return OrganizationModel.with_data(response, model_type) + + + def delete( + self + ) -> Dict[str, Any]: + """ + Delete the current organization. All projects that belong to the organization are deleted as well. + + Returns + ------- + Dict[str, Any] + API response as a dictionary + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization' + api_params = {} + + response = self.client.call('delete', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + }, api_params) + + return response + + def list_keys( self, queries: Optional[List[str]] = None, @@ -74,7 +186,7 @@ def create_key( name : str Key name. Max length: 128 chars. scopes : List[OrganizationKeyScopes] - Key scopes list. Maximum of 100 scopes are allowed. + Key scopes list. Maximum of 200 scopes are allowed. expire : Optional[str] Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration. @@ -171,7 +283,7 @@ def update_key( name : str Key name. Max length: 128 chars. scopes : List[OrganizationKeyScopes] - Key scopes list. Maximum of 100 scopes are allowed. + Key scopes list. Maximum of 200 scopes are allowed. expire : Optional[str] Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration. @@ -251,6 +363,243 @@ def delete_key( return response + def list_memberships( + self, + queries: Optional[List[str]] = None, + search: Optional[str] = None, + total: Optional[bool] = None + ) -> MembershipList: + """ + Get a list of all memberships from the current organization. + + Parameters + ---------- + queries : Optional[List[str]] + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles + search : Optional[str] + Search term to filter your list results. Max length: 256 chars. + total : Optional[bool] + When set to false, the total count returned will be 0 and will not be calculated. + + Returns + ------- + MembershipList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/memberships' + api_params = {} + + if queries is not None: + api_params['queries'] = self._normalize_value(queries) + if search is not None: + api_params['search'] = self._normalize_value(search) + if total is not None: + api_params['total'] = self._normalize_value(total) + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=MembershipList) + + + def create_membership( + self, + roles: List[str], + email: Optional[str] = None, + user_id: Optional[str] = None, + phone: Optional[str] = None, + url: Optional[str] = None, + name: Optional[str] = None + ) -> Membership: + """ + Invite a new member to join the current organization. An email with a link to join the organization will be sent to the new member's email address. If member doesn't exist in the project it will be automatically created. + + Parameters + ---------- + roles : List[str] + Array of strings. Use this param to set the user roles in the organization. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 81 characters long. + email : Optional[str] + Email of the new organization member. + user_id : Optional[str] + ID of the user to be added to the organization. + phone : Optional[str] + Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212. + url : Optional[str] + URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. + name : Optional[str] + Name of the new organization member. Max length: 128 chars. + + Returns + ------- + Membership + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/memberships' + api_params = {} + if roles is None: + raise AppwriteException('Missing required parameter: "roles"') + + + if email is not None: + api_params['email'] = self._normalize_value(email) + if user_id is not None: + api_params['userId'] = self._normalize_value(user_id) + if phone is not None: + api_params['phone'] = self._normalize_value(phone) + api_params['roles'] = self._normalize_value(roles) + if url is not None: + api_params['url'] = self._normalize_value(url) + if name is not None: + api_params['name'] = self._normalize_value(name) + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Membership) + + + def get_membership( + self, + membership_id: str + ) -> Membership: + """ + Get a membership from the current organization by its unique ID. + + Parameters + ---------- + membership_id : str + Membership ID. + + Returns + ------- + Membership + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/memberships/{membershipId}' + api_params = {} + if membership_id is None: + raise AppwriteException('Missing required parameter: "membership_id"') + + api_path = api_path.replace('{membershipId}', str(self._normalize_value(membership_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Membership) + + + def update_membership( + self, + membership_id: str, + roles: List[str] + ) -> Membership: + """ + Modify the roles of a member in the current organization. + + Parameters + ---------- + membership_id : str + Membership ID. + roles : List[str] + An array of strings. Use this param to set the user's roles in the organization. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 81 characters long. + + Returns + ------- + Membership + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/memberships/{membershipId}' + api_params = {} + if membership_id is None: + raise AppwriteException('Missing required parameter: "membership_id"') + + if roles is None: + raise AppwriteException('Missing required parameter: "roles"') + + api_path = api_path.replace('{membershipId}', str(self._normalize_value(membership_id))) + + api_params['roles'] = self._normalize_value(roles) + + response = self.client.call('patch', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Membership) + + + def delete_membership( + self, + membership_id: str + ) -> Dict[str, Any]: + """ + Remove a member from the current organization. The member is removed whether they accepted the invitation or not; a pending invitation is revoked. + + Parameters + ---------- + membership_id : str + Membership ID. + + Returns + ------- + Dict[str, Any] + API response as a dictionary + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/memberships/{membershipId}' + api_params = {} + if membership_id is None: + raise AppwriteException('Missing required parameter: "membership_id"') + + api_path = api_path.replace('{membershipId}', str(self._normalize_value(membership_id))) + + + response = self.client.call('delete', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + }, api_params) + + return response + + def list_projects( self, queries: Optional[List[str]] = None, @@ -263,7 +612,7 @@ def list_projects( Parameters ---------- queries : Optional[List[str]] - Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, teamId, labels, search + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, teamId, labels, search, accessedAt search : Optional[str] Search term to filter your list results. Max length: 256 chars. total : Optional[bool] diff --git a/appwrite/services/project.py b/appwrite/services/project.py index cccde7a9..c9d5c9d4 100644 --- a/appwrite/services/project.py +++ b/appwrite/services/project.py @@ -14,6 +14,7 @@ from ..models.o_auth2_provider_list import OAuth2ProviderList from ..models.o_auth2_amazon import OAuth2Amazon from ..models.o_auth2_apple import OAuth2Apple +from ..models.o_auth2_appwrite import OAuth2Appwrite from ..models.o_auth2_auth0 import OAuth2Auth0 from ..models.o_auth2_authentik import OAuth2Authentik from ..models.o_auth2_autodesk import OAuth2Autodesk @@ -37,6 +38,7 @@ from ..models.o_auth2_linkedin import OAuth2Linkedin from ..models.o_auth2_microsoft import OAuth2Microsoft from ..models.o_auth2_notion import OAuth2Notion +from ..enums.project_o_auth2_oidc_prompt import ProjectOAuth2OidcPrompt from ..models.o_auth2_oidc import OAuth2Oidc from ..models.o_auth2_okta import OAuth2Okta from ..models.o_auth2_paypal import OAuth2Paypal @@ -254,7 +256,7 @@ def create_key( name : str Key name. Max length: 128 chars. scopes : List[ProjectKeyScopes] - Key scopes list. Maximum of 100 scopes are allowed. + Key scopes list. Maximum of 200 scopes are allowed. expire : Optional[str] Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration. @@ -308,7 +310,7 @@ def create_ephemeral_key( Parameters ---------- scopes : List[ProjectKeyScopes] - Key scopes list. Maximum of 100 scopes are allowed. + Key scopes list. Maximum of 200 scopes are allowed. duration : float Time in seconds before ephemeral key expires. Maximum duration is 3600 seconds. @@ -400,7 +402,7 @@ def update_key( name : str Key name. Max length: 128 chars. scopes : List[ProjectKeyScopes] - Key scopes list. Maximum of 100 scopes are allowed. + Key scopes list. Maximum of 200 scopes are allowed. expire : Optional[str] Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration. @@ -790,7 +792,8 @@ def update_o_auth2_server( verification_url: Optional[str] = None, user_code_length: Optional[float] = None, user_code_format: Optional[str] = None, - device_code_duration: Optional[float] = None + device_code_duration: Optional[float] = None, + default_scopes: Optional[List[str]] = None ) -> ProjectModel: """ Update the OAuth2 server (OIDC provider) configuration. @@ -823,6 +826,8 @@ def update_o_auth2_server( Character set for device flow user codes: `numeric` (digits only — best for numeric keypads and TV remotes), `alphabetic` (letters only), or `alphanumeric` (letters and digits — highest entropy per character). Defaults to `alphanumeric`. device_code_duration : Optional[float] Lifetime in seconds of device flow device codes and user codes. Device codes are intentionally short-lived. Leave empty to use default 600. + default_scopes : Optional[List[str]] + List of OAuth2 scopes used when an authorization request omits the scope parameter. Every default scope must also be allowed by the OAuth2 server. Maximum of 100 scopes are allowed, each up to 128 characters long. Returns ------- @@ -861,6 +866,8 @@ def update_o_auth2_server( if user_code_format is not None: api_params['userCodeFormat'] = self._normalize_value(user_code_format) api_params['deviceCodeDuration'] = self._normalize_value(device_code_duration) + if default_scopes is not None: + api_params['defaultScopes'] = self._normalize_value(default_scopes) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -969,6 +976,51 @@ def update_o_auth2_apple( return self._parse_response(response, model=OAuth2Apple) + def update_o_auth2_appwrite( + self, + client_id: Optional[str] = None, + client_secret: Optional[str] = None, + enabled: Optional[bool] = None + ) -> OAuth2Appwrite: + """ + Update the project OAuth2 Appwrite configuration. + + Parameters + ---------- + client_id : Optional[str] + 'Client ID' of Appwrite OAuth2 app. For example: 6a42000000000000b5a0 + client_secret : Optional[str] + 'Client Secret' of Appwrite OAuth2 app. For example: b86afd000000000000000000000000000000000000000000000000000ced5f93 + enabled : Optional[bool] + OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid. + + Returns + ------- + OAuth2Appwrite + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/project/oauth2/appwrite' + api_params = {} + + api_params['clientId'] = self._normalize_value(client_id) + api_params['clientSecret'] = self._normalize_value(client_secret) + api_params['enabled'] = self._normalize_value(enabled) + + response = self.client.call('patch', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=OAuth2Appwrite) + + def update_o_auth2_auth0( self, client_id: Optional[str] = None, @@ -1999,6 +2051,8 @@ def update_o_auth2_oidc( authorization_url: Optional[str] = None, token_url: Optional[str] = None, user_info_url: Optional[str] = None, + prompt: Optional[List[ProjectOAuth2OidcPrompt]] = None, + max_age: Optional[float] = None, enabled: Optional[bool] = None ) -> OAuth2Oidc: """ @@ -2018,6 +2072,10 @@ def update_o_auth2_oidc( OpenID Connect token endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/token user_info_url : Optional[str] OpenID Connect user info endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/userinfo + prompt : Optional[List[ProjectOAuth2OidcPrompt]] + Array of OpenID Connect prompt values controlling the authentication and consent screens. If "none" is included, it must be the only element. "none" means: don't display any authentication or consent screens. "login" means: prompt the user to re-authenticate. "consent" means: prompt the user for consent. "select_account" means: prompt the user to select an account. + max_age : Optional[float] + Maximum authentication age in seconds. When set, the user must have authenticated within this many seconds, otherwise they are prompted to re-authenticate. enabled : Optional[bool] OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid. @@ -2041,6 +2099,8 @@ def update_o_auth2_oidc( api_params['authorizationURL'] = self._normalize_value(authorization_url) api_params['tokenURL'] = self._normalize_value(token_url) api_params['userInfoURL'] = self._normalize_value(user_info_url) + api_params['prompt'] = self._normalize_value(prompt) + api_params['maxAge'] = self._normalize_value(max_age) api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { diff --git a/appwrite/services/sites.py b/appwrite/services/sites.py index ce0c23f1..4daf161f 100644 --- a/appwrite/services/sites.py +++ b/appwrite/services/sites.py @@ -258,11 +258,17 @@ def list_frameworks( def list_specifications( - self + self, + type: Optional[str] = None ) -> SpecificationList: """ List allowed site specifications for this instance. + Parameters + ---------- + type : Optional[str] + Specification type to list. Can be one of: runtimes, builds. + Returns ------- SpecificationList @@ -277,6 +283,9 @@ def list_specifications( api_path = '/sites/specifications' api_params = {} + if type is not None: + api_params['type'] = self._normalize_value(type) + response = self.client.call('get', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), 'accept': 'application/json', @@ -460,8 +469,7 @@ def update( api_params['providerRootDirectory'] = self._normalize_value(provider_root_directory) api_params['providerBranches'] = self._normalize_value(provider_branches) api_params['providerPaths'] = self._normalize_value(provider_paths) - if build_specification is not None: - api_params['buildSpecification'] = self._normalize_value(build_specification) + api_params['buildSpecification'] = self._normalize_value(build_specification) if runtime_specification is not None: api_params['runtimeSpecification'] = self._normalize_value(runtime_specification) if deployment_retention is not None: diff --git a/appwrite/services/tables_db.py b/appwrite/services/tables_db.py index 7ea86989..9733bc96 100644 --- a/appwrite/services/tables_db.py +++ b/appwrite/services/tables_db.py @@ -96,7 +96,7 @@ def create( database_id: str, name: str, enabled: Optional[bool] = None, - dedicated_database_id: Optional[str] = None + specification: Optional[str] = None ) -> Database: """ Create a new Database. @@ -110,8 +110,8 @@ def create( Database name. Max length: 128 chars. enabled : Optional[bool] Is the database enabled? When set to 'disabled', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled. - dedicated_database_id : Optional[str] - Optional dedicated database (compute) ID to attach this database to. Leave empty to create a database on the shared pool. + specification : Optional[str] + Database specification. Defaults to `serverless`, which creates the database on the shared pool. Any other value provisions a dedicated database on that specification. Returns ------- @@ -137,8 +137,8 @@ def create( api_params['name'] = self._normalize_value(name) if enabled is not None: api_params['enabled'] = self._normalize_value(enabled) - if dedicated_database_id is not None: - api_params['dedicatedDatabaseId'] = self._normalize_value(dedicated_database_id) + if specification is not None: + api_params['specification'] = self._normalize_value(specification) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/usage.py b/appwrite/services/usage.py deleted file mode 100644 index de300c7f..00000000 --- a/appwrite/services/usage.py +++ /dev/null @@ -1,202 +0,0 @@ -from ..service import Service -from urllib.parse import quote -from typing import Any, Dict, List, Optional, Union -from ..exception import AppwriteException -from appwrite.utils.deprecated import deprecated -from ..models.usage_event_list import UsageEventList -from ..models.usage_gauge_list import UsageGaugeList - -class Usage(Service): - - def __init__(self, client) -> None: - super(Usage, self).__init__(client) - - def list_events( - self, - metrics: List[str], - resource: Optional[str] = None, - resource_id: Optional[str] = None, - interval: Optional[str] = None, - dimensions: Optional[List[str]] = None, - start_at: Optional[str] = None, - end_at: Optional[str] = None, - order_by: Optional[str] = None, - order_dir: Optional[str] = None, - limit: Optional[float] = None, - offset: Optional[float] = None - ) -> UsageEventList: - """ - Aggregate usage event metrics. `metrics[]` (1-10) is required; the response always contains one entry per requested metric, each with its own `points[]` time series. - - **Two response shapes**: - - Omit `interval` for a flat top-N table — one point per dimension combination, no time axis. Useful for "top 10 paths by bandwidth in the last 7 days". - - Pass `interval` (`1m`, `15m`, `30m`, `1h`, `1d`) for a time series — one point per (time bucket × dimension combination). - - `dimensions[]` breaks each point down by one or more attributes (service, path, status, country, …). Pass multiple metrics to render stacked charts in one round-trip. `resource` and `resourceId` filter the underlying events. `orderBy=value`+`orderDir=desc`+`limit=N` returns the top-N by aggregated value. When `startAt` is omitted, the default window adapts to `interval` (or 7d when interval is omitted). - - Parameters - ---------- - metrics : List[str] - One to ten metric names. Single-metric callers pass a one-element array. Example: `metrics[]=executions` or `metrics[]=executions&metrics[]=executions.compute` for stacked charts. - resource : Optional[str] - Resource type filter (singular form). Common values: function, site, database, bucket, file, webhook, team, user, project. - resource_id : Optional[str] - Resource id filter. - interval : Optional[str] - Time interval size. Omit (null) for a flat aggregate over the whole window. Allowed: 1m, 15m, 30m, 1h, 1d. - dimensions : Optional[List[str]] - Break-down dimensions (max 10). Allowed: path, method, status, service, country, region, hostname, osName, clientType, clientName, deviceName, teamId, resourceId. - start_at : Optional[str] - Range start in ISO 8601. Defaults adapt to interval (7d for the no-interval aggregate). - end_at : Optional[str] - Range end in ISO 8601. Defaults to the current time. - order_by : Optional[str] - Column to order by. Allowed: time, value. Default time when an interval is set; otherwise value. - order_dir : Optional[str] - Sort direction: asc or desc. Default desc — paired with the default limit, returns the most recent / highest-value groups first. - limit : Optional[float] - Maximum rows to return (1-5000). - offset : Optional[float] - Pagination offset (0-100000). - - Returns - ------- - UsageEventList - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/usage/events' - api_params = {} - if metrics is None: - raise AppwriteException('Missing required parameter: "metrics"') - - - api_params['metrics'] = self._normalize_value(metrics) - if resource is not None: - api_params['resource'] = self._normalize_value(resource) - if resource_id is not None: - api_params['resourceId'] = self._normalize_value(resource_id) - if interval is not None: - api_params['interval'] = self._normalize_value(interval) - if dimensions is not None: - api_params['dimensions'] = self._normalize_value(dimensions) - if start_at is not None: - api_params['startAt'] = self._normalize_value(start_at) - if end_at is not None: - api_params['endAt'] = self._normalize_value(end_at) - if order_by is not None: - api_params['orderBy'] = self._normalize_value(order_by) - if order_dir is not None: - api_params['orderDir'] = self._normalize_value(order_dir) - if limit is not None: - api_params['limit'] = self._normalize_value(limit) - if offset is not None: - api_params['offset'] = self._normalize_value(offset) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=UsageEventList) - - - def list_gauges( - self, - metrics: List[str], - resource_id: Optional[str] = None, - team_id: Optional[str] = None, - interval: Optional[str] = None, - dimensions: Optional[List[str]] = None, - start_at: Optional[str] = None, - end_at: Optional[str] = None, - order_by: Optional[str] = None, - order_dir: Optional[str] = None, - limit: Optional[float] = None, - offset: Optional[float] = None - ) -> UsageGaugeList: - """ - Aggregate usage gauge snapshots. Gauges are point-in-time values (storage totals, resource counts, …); each point carries the latest snapshot in its interval via `argMax(value, time)`. `metrics[]` (1-10) is required; the response always contains one entry per requested metric, each with its own `points[]` time series. - - **Two response shapes**: - - Omit `interval` for a flat top-N table — `argMax(value, time)` per dimension combination over the whole window, no time axis. Useful for "top 10 resources by current storage". - - Pass `interval` (`1m`, `15m`, `30m`, `1h`, `1d`) for a time series — one snapshot per (time bucket × dimension combination). - - `dimensions[]` breaks each point down further. Supported on gauges: `resourceId`, `teamId`, `service`, `resource`. `service` and `resource` enable per-service / per-resource-type panels (e.g. storage-by-service: group `files.storage`, `deployments.storage`, `builds.storage`, `databases.storage` by `service`). Pass multiple metrics to render stacked charts in one round-trip. `resourceId` and `teamId` parameters filter the underlying rows. `orderBy=value`+`orderDir=desc`+`limit=N` returns the top-N. When `startAt` is omitted, the default window adapts to interval (or 7d when interval is omitted). - - Parameters - ---------- - metrics : List[str] - One to ten metric names. Single-metric callers pass a one-element array. Example: `metrics[]=files.storage` or `metrics[]=files.storage&metrics[]=deployments.storage` for stacked charts. - resource_id : Optional[str] - Resource id filter. - team_id : Optional[str] - Team id filter. - interval : Optional[str] - Time interval size. Omit (null) for a flat aggregate over the whole window. Allowed: 1m, 15m, 30m, 1h, 1d. - dimensions : Optional[List[str]] - Break-down dimensions. Allowed: resourceId, teamId, service, resource. - start_at : Optional[str] - Range start in ISO 8601. Defaults to endAt - 7d. - end_at : Optional[str] - Range end in ISO 8601. Defaults to the current time. - order_by : Optional[str] - Column to order by. Allowed: time, value. Default time. - order_dir : Optional[str] - Sort direction: asc or desc. Default desc — paired with the default limit, this returns the most recent groups first. Pass asc for chronological charting. - limit : Optional[float] - Maximum rows to return (1-5000). - offset : Optional[float] - Pagination offset (0-100000). - - Returns - ------- - UsageGaugeList - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/usage/gauges' - api_params = {} - if metrics is None: - raise AppwriteException('Missing required parameter: "metrics"') - - - api_params['metrics'] = self._normalize_value(metrics) - if resource_id is not None: - api_params['resourceId'] = self._normalize_value(resource_id) - if team_id is not None: - api_params['teamId'] = self._normalize_value(team_id) - if interval is not None: - api_params['interval'] = self._normalize_value(interval) - if dimensions is not None: - api_params['dimensions'] = self._normalize_value(dimensions) - if start_at is not None: - api_params['startAt'] = self._normalize_value(start_at) - if end_at is not None: - api_params['endAt'] = self._normalize_value(end_at) - if order_by is not None: - api_params['orderBy'] = self._normalize_value(order_by) - if order_dir is not None: - api_params['orderDir'] = self._normalize_value(order_dir) - if limit is not None: - api_params['limit'] = self._normalize_value(limit) - if offset is not None: - api_params['offset'] = self._normalize_value(offset) - - response = self.client.call('get', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=UsageGaugeList) - diff --git a/appwrite/services/users.py b/appwrite/services/users.py index 00bd630f..c0e282e6 100644 --- a/appwrite/services/users.py +++ b/appwrite/services/users.py @@ -41,7 +41,7 @@ def list( Parameters ---------- queries : Optional[List[str]] - Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, email, phone, status, passwordUpdate, registration, emailVerification, phoneVerification, labels, impersonator + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, email, phone, status, passwordUpdate, registration, emailVerification, phoneVerification, labels, impersonator, accessedAt search : Optional[str] Search term to filter your list results. Max length: 256 chars. total : Optional[bool] diff --git a/docs/examples/health/get-audits-db.md b/docs/examples/apps/update-labels.md similarity index 63% rename from docs/examples/health/get-audits-db.md rename to docs/examples/apps/update-labels.md index 774edec1..0601df3f 100644 --- a/docs/examples/health/get-audits-db.md +++ b/docs/examples/apps/update-labels.md @@ -1,16 +1,19 @@ ```python from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthStatusList +from appwrite.services.apps import Apps +from appwrite.models import App client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_key('') # Your secret API key -health = Health(client) +apps = Apps(client) -result: HealthStatusList = health.get_audits_db() +result: App = apps.update_labels( + app_id = '', + labels = [] +) print(result.model_dump()) ``` diff --git a/docs/examples/backups/create-restoration.md b/docs/examples/backups/create-restoration.md index e2966caa..03f3f0a9 100644 --- a/docs/examples/backups/create-restoration.md +++ b/docs/examples/backups/create-restoration.md @@ -15,7 +15,8 @@ result: BackupRestoration = backups.create_restoration( archive_id = '', services = [BackupServices.DATABASES], new_resource_id = '', # optional - new_resource_name = '' # optional + new_resource_name = '', # optional + new_specification = 'serverless' # optional ) print(result.model_dump()) diff --git a/docs/examples/functions/get-deployment-download.md b/docs/examples/functions/get-deployment-download.md index 7f6b2f6b..49df861b 100644 --- a/docs/examples/functions/get-deployment-download.md +++ b/docs/examples/functions/get-deployment-download.md @@ -13,6 +13,7 @@ functions = Functions(client) result: bytes = functions.get_deployment_download( function_id = '', deployment_id = '', - type = DeploymentDownloadType.SOURCE # optional + type = DeploymentDownloadType.SOURCE, # optional + token = '' # optional ) ``` diff --git a/docs/examples/functions/list-specifications.md b/docs/examples/functions/list-specifications.md index 5b76860d..7cf529a9 100644 --- a/docs/examples/functions/list-specifications.md +++ b/docs/examples/functions/list-specifications.md @@ -10,7 +10,9 @@ client.set_key('') # Your secret API key functions = Functions(client) -result: SpecificationList = functions.list_specifications() +result: SpecificationList = functions.list_specifications( + type = 'runtimes' # optional +) print(result.model_dump()) ``` diff --git a/docs/examples/health/get-antivirus.md b/docs/examples/health/get-antivirus.md deleted file mode 100644 index 1ab51503..00000000 --- a/docs/examples/health/get-antivirus.md +++ /dev/null @@ -1,16 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthAntivirus - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthAntivirus = health.get_antivirus() - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-certificate.md b/docs/examples/health/get-certificate.md deleted file mode 100644 index 53dde7fb..00000000 --- a/docs/examples/health/get-certificate.md +++ /dev/null @@ -1,18 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthCertificate - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthCertificate = health.get_certificate( - domain = '' # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-db.md b/docs/examples/health/get-db.md deleted file mode 100644 index 47b6dd98..00000000 --- a/docs/examples/health/get-db.md +++ /dev/null @@ -1,16 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthStatusList - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthStatusList = health.get_db() - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-failed-jobs.md b/docs/examples/health/get-failed-jobs.md deleted file mode 100644 index da08c619..00000000 --- a/docs/examples/health/get-failed-jobs.md +++ /dev/null @@ -1,20 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue -from appwrite.enums import HealthQueueName - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthQueue = health.get_failed_jobs( - name = HealthQueueName.V1_DATABASE, - threshold = None # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-pub-sub.md b/docs/examples/health/get-pub-sub.md deleted file mode 100644 index 588a2bac..00000000 --- a/docs/examples/health/get-pub-sub.md +++ /dev/null @@ -1,16 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthStatusList - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthStatusList = health.get_pub_sub() - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-queue-certificates.md b/docs/examples/health/get-queue-certificates.md deleted file mode 100644 index 6666463f..00000000 --- a/docs/examples/health/get-queue-certificates.md +++ /dev/null @@ -1,18 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthQueue = health.get_queue_certificates( - threshold = None # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-queue-databases.md b/docs/examples/health/get-queue-databases.md deleted file mode 100644 index 44e165ed..00000000 --- a/docs/examples/health/get-queue-databases.md +++ /dev/null @@ -1,19 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthQueue = health.get_queue_databases( - name = '', # optional - threshold = None # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-queue-deletes.md b/docs/examples/health/get-queue-deletes.md deleted file mode 100644 index fece6301..00000000 --- a/docs/examples/health/get-queue-deletes.md +++ /dev/null @@ -1,18 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthQueue = health.get_queue_deletes( - threshold = None # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-queue-functions.md b/docs/examples/health/get-queue-functions.md deleted file mode 100644 index 03d9afe8..00000000 --- a/docs/examples/health/get-queue-functions.md +++ /dev/null @@ -1,18 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthQueue = health.get_queue_functions( - threshold = None # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-queue-logs.md b/docs/examples/health/get-queue-logs.md deleted file mode 100644 index 5191fa5a..00000000 --- a/docs/examples/health/get-queue-logs.md +++ /dev/null @@ -1,18 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthQueue = health.get_queue_logs( - threshold = None # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-queue-mails.md b/docs/examples/health/get-queue-mails.md deleted file mode 100644 index aed05e1d..00000000 --- a/docs/examples/health/get-queue-mails.md +++ /dev/null @@ -1,18 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthQueue = health.get_queue_mails( - threshold = None # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-queue-messaging.md b/docs/examples/health/get-queue-messaging.md deleted file mode 100644 index 5da876a6..00000000 --- a/docs/examples/health/get-queue-messaging.md +++ /dev/null @@ -1,18 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthQueue = health.get_queue_messaging( - threshold = None # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-queue-migrations.md b/docs/examples/health/get-queue-migrations.md deleted file mode 100644 index 749675d7..00000000 --- a/docs/examples/health/get-queue-migrations.md +++ /dev/null @@ -1,18 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthQueue = health.get_queue_migrations( - threshold = None # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-queue-stats-resources.md b/docs/examples/health/get-queue-stats-resources.md deleted file mode 100644 index 60c0dd57..00000000 --- a/docs/examples/health/get-queue-stats-resources.md +++ /dev/null @@ -1,18 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthQueue = health.get_queue_stats_resources( - threshold = None # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-queue-usage.md b/docs/examples/health/get-queue-usage.md deleted file mode 100644 index 10fa4a0f..00000000 --- a/docs/examples/health/get-queue-usage.md +++ /dev/null @@ -1,18 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthQueue = health.get_queue_usage( - threshold = None # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-queue-webhooks.md b/docs/examples/health/get-queue-webhooks.md deleted file mode 100644 index 24423761..00000000 --- a/docs/examples/health/get-queue-webhooks.md +++ /dev/null @@ -1,18 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthQueue = health.get_queue_webhooks( - threshold = None # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-storage-local.md b/docs/examples/health/get-storage-local.md deleted file mode 100644 index 57b8ec40..00000000 --- a/docs/examples/health/get-storage-local.md +++ /dev/null @@ -1,16 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthStatus - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthStatus = health.get_storage_local() - -print(result.model_dump()) -``` diff --git a/docs/examples/health/get-storage.md b/docs/examples/health/get-storage.md deleted file mode 100644 index a17ee4e3..00000000 --- a/docs/examples/health/get-storage.md +++ /dev/null @@ -1,16 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthStatus - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -health = Health(client) - -result: HealthStatus = health.get_storage() - -print(result.model_dump()) -``` diff --git a/docs/examples/messaging/list-provider-logs.md b/docs/examples/messaging/list-provider-logs.md deleted file mode 100644 index ad271428..00000000 --- a/docs/examples/messaging/list-provider-logs.md +++ /dev/null @@ -1,20 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.messaging import Messaging -from appwrite.models import LogList - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -messaging = Messaging(client) - -result: LogList = messaging.list_provider_logs( - provider_id = '', - queries = [], # optional - total = False # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/messaging/list-subscriber-logs.md b/docs/examples/messaging/list-subscriber-logs.md deleted file mode 100644 index b02c2b1b..00000000 --- a/docs/examples/messaging/list-subscriber-logs.md +++ /dev/null @@ -1,20 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.messaging import Messaging -from appwrite.models import LogList - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -messaging = Messaging(client) - -result: LogList = messaging.list_subscriber_logs( - subscriber_id = '', - queries = [], # optional - total = False # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/messaging/list-topic-logs.md b/docs/examples/messaging/list-topic-logs.md deleted file mode 100644 index 3ed2d7e3..00000000 --- a/docs/examples/messaging/list-topic-logs.md +++ /dev/null @@ -1,20 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.messaging import Messaging -from appwrite.models import LogList - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -messaging = Messaging(client) - -result: LogList = messaging.list_topic_logs( - topic_id = '', - queries = [], # optional - total = False # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/oauth2/approve.md b/docs/examples/oauth2/approve.md index b3313599..12bc9c0d 100644 --- a/docs/examples/oauth2/approve.md +++ b/docs/examples/oauth2/approve.md @@ -12,7 +12,8 @@ oauth2 = Oauth2(client) result: Oauth2Approve = oauth2.approve( grant_id = '', - authorization_details = '' # optional + authorization_details = '', # optional + scope = '' # optional ) print(result.model_dump()) diff --git a/docs/examples/oauth2/authorize.md b/docs/examples/oauth2/authorize.md index 3ea83a66..9d7bb486 100644 --- a/docs/examples/oauth2/authorize.md +++ b/docs/examples/oauth2/authorize.md @@ -11,17 +11,20 @@ client.set_project('') # Your project ID oauth2 = Oauth2(client) result: Oauth2Authorize = oauth2.authorize( - client_id = '', - redirect_uri = 'https://example.com', - response_type = 'code', - scope = '', + client_id = '', # optional + redirect_uri = 'https://example.com', # optional + response_type = '', # optional + scope = '', # optional state = '', # optional nonce = '', # optional code_challenge = '', # optional code_challenge_method = 's256', # optional prompt = '', # optional max_age = 0, # optional - authorization_details = '' # optional + authorization_details = '', # optional + resource = '', # optional + audience = '', # optional + request_uri = '' # optional ) print(result.model_dump()) diff --git a/docs/examples/oauth2/create-device-authorization.md b/docs/examples/oauth2/create-device-authorization.md index 4ba20f77..0fa3852d 100644 --- a/docs/examples/oauth2/create-device-authorization.md +++ b/docs/examples/oauth2/create-device-authorization.md @@ -13,7 +13,9 @@ oauth2 = Oauth2(client) result: Oauth2DeviceAuthorization = oauth2.create_device_authorization( client_id = '', # optional scope = '', # optional - authorization_details = '' # optional + authorization_details = '', # optional + resource = '', # optional + audience = '' # optional ) print(result.model_dump()) diff --git a/docs/examples/oauth2/create-par.md b/docs/examples/oauth2/create-par.md new file mode 100644 index 00000000..2018794c --- /dev/null +++ b/docs/examples/oauth2/create-par.md @@ -0,0 +1,30 @@ +```python +from appwrite.client import Client +from appwrite.services.oauth2 import Oauth2 +from appwrite.models import Oauth2PAR + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_session('') # The user session to authenticate with +client.set_project('') # Your project ID + +oauth2 = Oauth2(client) + +result: Oauth2PAR = oauth2.create_par( + client_id = '', + redirect_uri = 'https://example.com', + response_type = 'code', + scope = '', # optional + state = '', # optional + nonce = '', # optional + code_challenge = '', # optional + code_challenge_method = 's256', # optional + prompt = '', # optional + max_age = 0, # optional + authorization_details = '', # optional + resource = '', # optional + audience = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/oauth2/create-token.md b/docs/examples/oauth2/create-token.md index ac698593..1d52bbc9 100644 --- a/docs/examples/oauth2/create-token.md +++ b/docs/examples/oauth2/create-token.md @@ -18,7 +18,9 @@ result: Oauth2Token = oauth2.create_token( client_id = '', # optional client_secret = '', # optional code_verifier = '', # optional - redirect_uri = 'https://example.com' # optional + redirect_uri = 'https://example.com', # optional + resource = '', # optional + audience = '' # optional ) print(result.model_dump()) diff --git a/docs/examples/oauth2/list-organizations.md b/docs/examples/oauth2/list-organizations.md new file mode 100644 index 00000000..42ece3a8 --- /dev/null +++ b/docs/examples/oauth2/list-organizations.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.oauth2 import Oauth2 +from appwrite.models import Oauth2OrganizationList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_session('') # The user session to authenticate with +client.set_project('') # Your project ID + +oauth2 = Oauth2(client) + +result: Oauth2OrganizationList = oauth2.list_organizations( + limit = 1, # optional + offset = 0, # optional + search = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/oauth2/list-projects.md b/docs/examples/oauth2/list-projects.md new file mode 100644 index 00000000..97a58b7b --- /dev/null +++ b/docs/examples/oauth2/list-projects.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.oauth2 import Oauth2 +from appwrite.models import Oauth2ProjectList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_session('') # The user session to authenticate with +client.set_project('') # Your project ID + +oauth2 = Oauth2(client) + +result: Oauth2ProjectList = oauth2.list_projects( + limit = 1, # optional + offset = 0, # optional + search = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/organization/create-membership.md b/docs/examples/organization/create-membership.md new file mode 100644 index 00000000..0801bdd7 --- /dev/null +++ b/docs/examples/organization/create-membership.md @@ -0,0 +1,23 @@ +```python +from appwrite.client import Client +from appwrite.services.organization import Organization +from appwrite.models import Membership + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +organization = Organization(client) + +result: Membership = organization.create_membership( + roles = [], + email = 'email@example.com', # optional + user_id = '', # optional + phone = '+12065550100', # optional + url = 'https://example.com', # optional + name = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/health/get.md b/docs/examples/organization/delete-membership.md similarity index 60% rename from docs/examples/health/get.md rename to docs/examples/organization/delete-membership.md index 5a3b3d06..dee81d19 100644 --- a/docs/examples/health/get.md +++ b/docs/examples/organization/delete-membership.md @@ -1,16 +1,15 @@ ```python from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthStatus +from appwrite.services.organization import Organization client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_key('') # Your secret API key -health = Health(client) +organization = Organization(client) -result: HealthStatus = health.get() - -print(result.model_dump()) +result = organization.delete_membership( + membership_id = '' +) ``` diff --git a/docs/examples/health/get-time.md b/docs/examples/organization/delete.md similarity index 60% rename from docs/examples/health/get-time.md rename to docs/examples/organization/delete.md index 5ffae25e..94b7c2a7 100644 --- a/docs/examples/health/get-time.md +++ b/docs/examples/organization/delete.md @@ -1,16 +1,13 @@ ```python from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthTime +from appwrite.services.organization import Organization client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_key('') # Your secret API key -health = Health(client) +organization = Organization(client) -result: HealthTime = health.get_time() - -print(result.model_dump()) +result = organization.delete() ``` diff --git a/docs/examples/health/get-queue-audits.md b/docs/examples/organization/get-membership.md similarity index 57% rename from docs/examples/health/get-queue-audits.md rename to docs/examples/organization/get-membership.md index a892ca25..b3fcd9de 100644 --- a/docs/examples/health/get-queue-audits.md +++ b/docs/examples/organization/get-membership.md @@ -1,17 +1,17 @@ ```python from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue +from appwrite.services.organization import Organization +from appwrite.models import Membership client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_key('') # Your secret API key -health = Health(client) +organization = Organization(client) -result: HealthQueue = health.get_queue_audits( - threshold = None # optional +result: Membership = organization.get_membership( + membership_id = '' ) print(result.model_dump()) diff --git a/docs/examples/health/get-cache.md b/docs/examples/organization/get.md similarity index 59% rename from docs/examples/health/get-cache.md rename to docs/examples/organization/get.md index 39050878..b99b6a7f 100644 --- a/docs/examples/health/get-cache.md +++ b/docs/examples/organization/get.md @@ -1,16 +1,16 @@ ```python from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthStatusList +from appwrite.services.organization import Organization +from appwrite.models import Organization as OrganizationModel client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_key('') # Your secret API key -health = Health(client) +organization = Organization(client) -result: HealthStatusList = health.get_cache() +result: OrganizationModel = organization.get() print(result.model_dump()) ``` diff --git a/docs/examples/messaging/list-message-logs.md b/docs/examples/organization/list-memberships.md similarity index 60% rename from docs/examples/messaging/list-message-logs.md rename to docs/examples/organization/list-memberships.md index 27d6b9b6..e49e1793 100644 --- a/docs/examples/messaging/list-message-logs.md +++ b/docs/examples/organization/list-memberships.md @@ -1,18 +1,18 @@ ```python from appwrite.client import Client -from appwrite.services.messaging import Messaging -from appwrite.models import LogList +from appwrite.services.organization import Organization +from appwrite.models import MembershipList client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_key('') # Your secret API key -messaging = Messaging(client) +organization = Organization(client) -result: LogList = messaging.list_message_logs( - message_id = '', +result: MembershipList = organization.list_memberships( queries = [], # optional + search = '', # optional total = False # optional ) diff --git a/docs/examples/health/get-console-pausing.md b/docs/examples/organization/update-membership.md similarity index 55% rename from docs/examples/health/get-console-pausing.md rename to docs/examples/organization/update-membership.md index 633c175a..a4f9c5a0 100644 --- a/docs/examples/health/get-console-pausing.md +++ b/docs/examples/organization/update-membership.md @@ -1,18 +1,18 @@ ```python from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthStatus +from appwrite.services.organization import Organization +from appwrite.models import Membership client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_key('') # Your secret API key -health = Health(client) +organization = Organization(client) -result: HealthStatus = health.get_console_pausing( - threshold = None, # optional - inactivity_days = None # optional +result: Membership = organization.update_membership( + membership_id = '', + roles = [] ) print(result.model_dump()) diff --git a/docs/examples/health/get-queue-builds.md b/docs/examples/organization/update.md similarity index 56% rename from docs/examples/health/get-queue-builds.md rename to docs/examples/organization/update.md index 0ae87b2c..6d456c57 100644 --- a/docs/examples/health/get-queue-builds.md +++ b/docs/examples/organization/update.md @@ -1,17 +1,17 @@ ```python from appwrite.client import Client -from appwrite.services.health import Health -from appwrite.models import HealthQueue +from appwrite.services.organization import Organization +from appwrite.models import Organization as OrganizationModel client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_key('') # Your secret API key -health = Health(client) +organization = Organization(client) -result: HealthQueue = health.get_queue_builds( - threshold = None # optional +result: OrganizationModel = organization.update( + name = '' ) print(result.model_dump()) diff --git a/docs/examples/project/update-o-auth-2-appwrite.md b/docs/examples/project/update-o-auth-2-appwrite.md new file mode 100644 index 00000000..7b765921 --- /dev/null +++ b/docs/examples/project/update-o-auth-2-appwrite.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.project import Project +from appwrite.models import OAuth2Appwrite + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +project = Project(client) + +result: OAuth2Appwrite = project.update_o_auth2_appwrite( + client_id = '', # optional + client_secret = '', # optional + enabled = False # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/project/update-o-auth-2-oidc.md b/docs/examples/project/update-o-auth-2-oidc.md index 3913262d..e2df2e3c 100644 --- a/docs/examples/project/update-o-auth-2-oidc.md +++ b/docs/examples/project/update-o-auth-2-oidc.md @@ -2,6 +2,7 @@ from appwrite.client import Client from appwrite.services.project import Project from appwrite.models import OAuth2Oidc +from appwrite.enums import ProjectOAuth2OidcPrompt client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint @@ -17,6 +18,8 @@ result: OAuth2Oidc = project.update_o_auth2_oidc( authorization_url = 'https://example.com', # optional token_url = 'https://example.com', # optional user_info_url = 'https://example.com', # optional + prompt = [ProjectOAuth2OidcPrompt.NONE], # optional + max_age = 0, # optional enabled = False # optional ) diff --git a/docs/examples/project/update-o-auth-2-server.md b/docs/examples/project/update-o-auth-2-server.md index 6c5b2578..6be24c54 100644 --- a/docs/examples/project/update-o-auth-2-server.md +++ b/docs/examples/project/update-o-auth-2-server.md @@ -23,7 +23,8 @@ result: ProjectModel = project.update_o_auth2_server( verification_url = 'https://example.com', # optional user_code_length = 6, # optional user_code_format = 'numeric', # optional - device_code_duration = 60 # optional + device_code_duration = 60, # optional + default_scopes = [] # optional ) print(result.model_dump()) diff --git a/docs/examples/sites/list-specifications.md b/docs/examples/sites/list-specifications.md index c1229a30..2f04ddf4 100644 --- a/docs/examples/sites/list-specifications.md +++ b/docs/examples/sites/list-specifications.md @@ -10,7 +10,9 @@ client.set_key('') # Your secret API key sites = Sites(client) -result: SpecificationList = sites.list_specifications() +result: SpecificationList = sites.list_specifications( + type = 'runtimes' # optional +) print(result.model_dump()) ``` diff --git a/docs/examples/tablesdb/create.md b/docs/examples/tablesdb/create.md index c50f28e7..a816905f 100644 --- a/docs/examples/tablesdb/create.md +++ b/docs/examples/tablesdb/create.md @@ -14,7 +14,7 @@ result: Database = tables_db.create( database_id = '', name = '', enabled = False, # optional - dedicated_database_id = '' # optional + specification = 'serverless' # optional ) print(result.model_dump()) diff --git a/docs/examples/usage/list-events.md b/docs/examples/usage/list-events.md deleted file mode 100644 index 83f8d725..00000000 --- a/docs/examples/usage/list-events.md +++ /dev/null @@ -1,28 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.usage import Usage -from appwrite.models import UsageEventList - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -usage = Usage(client) - -result: UsageEventList = usage.list_events( - metrics = [], - resource = '', # optional - resource_id = '', # optional - interval = '1m', # optional - dimensions = [], # optional - start_at = '2020-10-15T06:38:00.000+00:00', # optional - end_at = '2020-10-15T06:38:00.000+00:00', # optional - order_by = 'time', # optional - order_dir = 'asc', # optional - limit = 1, # optional - offset = 0 # optional -) - -print(result.model_dump()) -``` diff --git a/docs/examples/usage/list-gauges.md b/docs/examples/usage/list-gauges.md deleted file mode 100644 index fb221f9a..00000000 --- a/docs/examples/usage/list-gauges.md +++ /dev/null @@ -1,28 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.usage import Usage -from appwrite.models import UsageGaugeList - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -usage = Usage(client) - -result: UsageGaugeList = usage.list_gauges( - metrics = [], - resource_id = '', # optional - team_id = '', # optional - interval = '1m', # optional - dimensions = [], # optional - start_at = '2020-10-15T06:38:00.000+00:00', # optional - end_at = '2020-10-15T06:38:00.000+00:00', # optional - order_by = 'time', # optional - order_dir = 'asc', # optional - limit = 1, # optional - offset = 0 # optional -) - -print(result.model_dump()) -``` diff --git a/pyproject.toml b/pyproject.toml index 64acd0d7..10559426 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "appwrite" -version = "21.0.0" +version = "22.0.0" description = "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API" readme = "README.md" requires-python = ">=3.9" diff --git a/requirements.txt b/requirements.txt index 43f14202..b3386211 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ requests>=2.31,<3 -requests_mock==1.11.0 +requests_mock==1.12.1 pydantic>=2,<3 diff --git a/setup.py b/setup.py index bedddde5..9557066e 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name = 'appwrite', packages = setuptools.find_packages(), - version = '21.0.0', + version = '22.0.0', license='BSD-3-Clause', description = 'Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API', long_description = long_description, @@ -18,7 +18,7 @@ maintainer = 'Appwrite Team', maintainer_email = 'team@appwrite.io', url = 'https://appwrite.io/support', - download_url='https://github.com/appwrite/sdk-for-python/archive/21.0.0.tar.gz', + download_url='https://github.com/appwrite/sdk-for-python/archive/22.0.0.tar.gz', install_requires=[ 'requests', 'pydantic>=2,<3', diff --git a/test/services/test_activities.py b/test/services/test_activities.py index 41d9d8b1..9fcc5e4d 100644 --- a/test/services/test_activities.py +++ b/test/services/test_activities.py @@ -47,9 +47,7 @@ def test_get_event(self, m): "time": "2020-10-15T06:38:00.000+00:00", "projectId": "610fc2f985ee0", "teamId": "610fc2f985ee0", - "hostname": "appwrite.io", - "countryCode": "US", - "countryName": "United States" + "hostname": "appwrite.io" } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) diff --git a/test/services/test_apps.py b/test/services/test_apps.py index a8d5984d..4520a42b 100644 --- a/test/services/test_apps.py +++ b/test/services/test_apps.py @@ -42,6 +42,7 @@ def test_create(self, m): "contacts": [], "tagline": "Automate your workspace.", "tags": [], + "labels": [], "images": [], "supportUrl": "https:\/\/example.com\/support", "dataDeletionUrl": "https:\/\/example.com\/data-deletion", @@ -80,6 +81,7 @@ def test_get(self, m): "contacts": [], "tagline": "Automate your workspace.", "tags": [], + "labels": [], "images": [], "supportUrl": "https:\/\/example.com\/support", "dataDeletionUrl": "https:\/\/example.com\/data-deletion", @@ -116,6 +118,7 @@ def test_update(self, m): "contacts": [], "tagline": "Automate your workspace.", "tags": [], + "labels": [], "images": [], "supportUrl": "https:\/\/example.com\/support", "dataDeletionUrl": "https:\/\/example.com\/data-deletion", @@ -150,6 +153,44 @@ def test_delete(self, m): self.assertEqual(response, data) + @requests_mock.Mocker() + def test_update_labels(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "name": "My Application", + "description": "Connect your workspace to My Application.", + "clientUri": "https:\/\/example.com", + "logoUri": "https:\/\/example.com\/logo.png", + "privacyPolicyUrl": "https:\/\/example.com\/privacy", + "termsUrl": "https:\/\/example.com\/terms", + "contacts": [], + "tagline": "Automate your workspace.", + "tags": [], + "labels": [], + "images": [], + "supportUrl": "https:\/\/example.com\/support", + "dataDeletionUrl": "https:\/\/example.com\/data-deletion", + "redirectUris": [], + "postLogoutRedirectUris": [], + "enabled": True, + "type": "confidential", + "deviceFlow": True, + "teamId": "5e5ea5c16897e", + "userId": "5e5ea5c16897e", + "secrets": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.update_labels( + '', + [], + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_list_secrets(self, m): data = { @@ -236,6 +277,7 @@ def test_update_team(self, m): "contacts": [], "tagline": "Automate your workspace.", "tags": [], + "labels": [], "images": [], "supportUrl": "https:\/\/example.com\/support", "dataDeletionUrl": "https:\/\/example.com\/data-deletion", diff --git a/test/services/test_backups.py b/test/services/test_backups.py index 83c59835..dfa1f5a2 100644 --- a/test/services/test_backups.py +++ b/test/services/test_backups.py @@ -196,7 +196,7 @@ def test_create_restoration(self, m): "migrationId": "did8jx6ws45jana098ab7", "services": [], "resources": [], - "options": "{databases.database[{oldId, newId, newName}]}" + "options": "{databases.database[{oldId, newId, newName, newSpecification}]}" } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -235,7 +235,7 @@ def test_get_restoration(self, m): "migrationId": "did8jx6ws45jana098ab7", "services": [], "resources": [], - "options": "{databases.database[{oldId, newId, newName}]}" + "options": "{databases.database[{oldId, newId, newName, newSpecification}]}" } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) diff --git a/test/services/test_health.py b/test/services/test_health.py deleted file mode 100644 index 930aeb62..00000000 --- a/test/services/test_health.py +++ /dev/null @@ -1,361 +0,0 @@ -import json -import requests_mock -import unittest - -from appwrite.client import Client -from appwrite.input_file import InputFile -from appwrite.models import * -from appwrite.services.health import Health - -class HealthServiceTest(unittest.TestCase): - - def setUp(self): - self.client = Client() - self.health = Health(self.client) - - @requests_mock.Mocker() - def test_get(self, m): - data = { - "name": "database", - "ping": 128.0, - "status": "pass" -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_antivirus(self, m): - data = { - "version": "1.0.0", - "status": "online" -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_antivirus( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_audits_db(self, m): - data = { - "total": 5.0, - "statuses": [] -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_audits_db( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_cache(self, m): - data = { - "total": 5.0, - "statuses": [] -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_cache( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_certificate(self, m): - data = { - "name": "\/CN=www.google.com", - "subjectSN": "", - "issuerOrganisation": "", - "validFrom": "1704200998", - "validTo": "1711458597", - "signatureTypeSN": "RSA-SHA256" -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_certificate( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_console_pausing(self, m): - data = { - "name": "database", - "ping": 128.0, - "status": "pass" -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_console_pausing( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_db(self, m): - data = { - "total": 5.0, - "statuses": [] -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_db( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_pub_sub(self, m): - data = { - "total": 5.0, - "statuses": [] -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_pub_sub( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_audits(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_audits( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_builds(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_builds( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_certificates(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_certificates( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_databases(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_databases( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_deletes(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_deletes( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_failed_jobs(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_failed_jobs( - 'v1-database', - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_functions(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_functions( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_logs(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_logs( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_mails(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_mails( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_messaging(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_messaging( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_migrations(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_migrations( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_stats_resources(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_stats_resources( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_usage(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_usage( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_queue_webhooks(self, m): - data = { - "size": 8.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_queue_webhooks( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_storage(self, m): - data = { - "name": "database", - "ping": 128.0, - "status": "pass" -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_storage( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_storage_local(self, m): - data = { - "name": "database", - "ping": 128.0, - "status": "pass" -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_storage_local( - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_get_time(self, m): - data = { - "remoteTime": 1639490751.0, - "localTime": 1639490844.0, - "diff": 93.0 -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.health.get_time( - ) - - self.assertEqual(response.to_dict(), data) - diff --git a/test/services/test_messaging.py b/test/services/test_messaging.py index 1424b6a1..ef6d618d 100644 --- a/test/services/test_messaging.py +++ b/test/services/test_messaging.py @@ -250,21 +250,6 @@ def test_delete(self, m): self.assertEqual(response, data) - @requests_mock.Mocker() - def test_list_message_logs(self, m): - data = { - "total": 5.0, - "logs": [] -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.messaging.list_message_logs( - '', - ) - - self.assertEqual(response.to_dict(), data) - @requests_mock.Mocker() def test_list_targets(self, m): data = { @@ -974,36 +959,6 @@ def test_delete_provider(self, m): self.assertEqual(response, data) - @requests_mock.Mocker() - def test_list_provider_logs(self, m): - data = { - "total": 5.0, - "logs": [] -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.messaging.list_provider_logs( - '', - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_list_subscriber_logs(self, m): - data = { - "total": 5.0, - "logs": [] -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.messaging.list_subscriber_logs( - '', - ) - - self.assertEqual(response.to_dict(), data) - @requests_mock.Mocker() def test_list_topics(self, m): data = { @@ -1094,21 +1049,6 @@ def test_delete_topic(self, m): self.assertEqual(response, data) - @requests_mock.Mocker() - def test_list_topic_logs(self, m): - data = { - "total": 5.0, - "logs": [] -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.messaging.list_topic_logs( - '', - ) - - self.assertEqual(response.to_dict(), data) - @requests_mock.Mocker() def test_list_subscribers(self, m): data = { diff --git a/test/services/test_oauth2.py b/test/services/test_oauth2.py index 4096d693..3b0bc38e 100644 --- a/test/services/test_oauth2.py +++ b/test/services/test_oauth2.py @@ -37,10 +37,6 @@ def test_authorize(self, m): m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) response = self.oauth2.authorize( - '', - 'https://example.com', - 'code', - '', ) self.assertEqual(response.to_dict(), data) @@ -72,6 +68,7 @@ def test_create_grant(self, m): "userId": "5e5ea5c16897e", "appId": "5e5ea5c16897e", "scopes": [], + "resources": [], "authorizationDetails": "[{\"type\":\"calendar\",\"identifier\":\"primary\",\"actions\":[\"read_events\",\"create_event\"]}]", "prompt": "login", "redirectUri": "https:\/\/example.com\/callback", @@ -96,6 +93,7 @@ def test_get_grant(self, m): "userId": "5e5ea5c16897e", "appId": "5e5ea5c16897e", "scopes": [], + "resources": [], "authorizationDetails": "[{\"type\":\"calendar\",\"identifier\":\"primary\",\"actions\":[\"read_events\",\"create_event\"]}]", "prompt": "login", "redirectUri": "https:\/\/example.com\/callback", @@ -111,6 +109,51 @@ def test_get_grant(self, m): self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() + def test_list_organizations(self, m): + data = { + "total": 5.0, + "organizations": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.oauth2.list_organizations( + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_create_par(self, m): + data = { + "request_uri": "urn:appwrite:oauth2:request:5e5ea5c16897e", + "expires_in": 600.0 +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.oauth2.create_par( + '', + 'https://example.com', + 'code', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_list_projects(self, m): + data = { + "total": 5.0, + "projects": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.oauth2.list_projects( + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_reject(self, m): data = { diff --git a/test/services/test_organization.py b/test/services/test_organization.py index a4e32d24..7cd67f61 100644 --- a/test/services/test_organization.py +++ b/test/services/test_organization.py @@ -13,6 +13,468 @@ def setUp(self): self.client = Client() self.organization = Organization(self.client) + @requests_mock.Mocker() + def test_get(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "name": "VIP", + "total": 7.0, + "prefs": {}, + "billingBudget": 50.0, + "budgetAlerts": [], + "billingPlan": "tier-1", + "billingPlanId": "tier-1", + "billingPlanDetails": { + "$id": "tier-0", + "name": "Hobby", + "desc": "Hobby plan", + "order": 0.0, + "price": 25, + "trial": 14.0, + "bandwidth": 25.0, + "storage": 25.0, + "imageTransformations": 100.0, + "screenshotsGenerated": 50.0, + "members": 25.0, + "webhooks": 25.0, + "projects": 2.0, + "platforms": 3.0, + "users": 25.0, + "teams": 25.0, + "databases": 25.0, + "databasesReads": 500000.0, + "databasesWrites": 250000.0, + "databasesBatchSize": 100.0, + "buckets": 25.0, + "fileSize": 25.0, + "functions": 25.0, + "sites": 1.0, + "executions": 25.0, + "executionsRetentionCount": 10000.0, + "GBHours": 100.0, + "realtime": 25.0, + "realtimeMessages": 100000.0, + "messages": 1000.0, + "topics": 1.0, + "authPhone": 10.0, + "domains": 5.0, + "activityLogs": 7.0, + "usageLogs": 30.0, + "projectInactivityDays": 7.0, + "alertLimit": 80.0, + "usage": { + "bandwidth": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "executions": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "member": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "realtime": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "realtimeMessages": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "realtimeBandwidth": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "storage": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "users": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "GBHours": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "imageTransformations": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "credits": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + } + }, + "addons": { + "seats": { + "supported": True, + "planIncluded": 1.0, + "limit": 5.0, + "type": "numeric", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "projects": { + "supported": True, + "planIncluded": 1.0, + "limit": 5.0, + "type": "numeric", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + } + }, + "budgetCapEnabled": True, + "customSmtp": True, + "emailBranding": True, + "requiresPaymentMethod": True, + "requiresBillingAddress": True, + "isAvailable": True, + "selfService": True, + "premiumSupport": True, + "budgeting": True, + "supportsMockNumbers": True, + "supportsOrganizationRoles": True, + "supportsCredits": True, + "supportsDisposableEmailValidation": True, + "supportsCanonicalEmailValidation": True, + "supportsFreeEmailValidation": True, + "supportsCorporateEmailValidation": True, + "supportsProjectSpecificRoles": True, + "backupsEnabled": True, + "usagePerProject": True, + "supportedAddons": { + "baa": True, + "premiumGeoDB": True, + "premiumGeoDBOrg": True + }, + "backupPolicies": 1.0, + "deploymentSize": 30.0, + "buildSize": 2000.0, + "databasesAllowEncrypt": True, + "group": "pro" + }, + "billingEmail": "billing@org.example", + "billingStartDate": "2020-10-15T06:38:00.000+00:00", + "billingCurrentInvoiceDate": "2020-10-15T06:38:00.000+00:00", + "billingNextInvoiceDate": "2020-10-15T06:38:00.000+00:00", + "billingTrialStartDate": "2020-10-15T06:38:00.000+00:00", + "billingTrialDays": 14.0, + "billingAggregationId": "adbc3de4rddfsd", + "billingInvoiceId": "adbc3de4rddfsd", + "paymentMethodId": "adbc3de4rddfsd", + "billingAddressId": "adbc3de4rddfsd", + "backupPaymentMethodId": "adbc3de4rddfsd", + "status": "active", + "remarks": "Pending initial payment", + "agreementBAA": "", + "programManagerName": "", + "programManagerCalendar": "", + "programDiscordChannelName": "", + "programDiscordChannelUrl": "", + "billingPlanDowngrade": "tier-1", + "billingTaxId": "", + "markedForDeletion": True, + "platform": "imagine", + "projects": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.get( + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_update(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "name": "VIP", + "total": 7.0, + "prefs": {}, + "billingBudget": 50.0, + "budgetAlerts": [], + "billingPlan": "tier-1", + "billingPlanId": "tier-1", + "billingPlanDetails": { + "$id": "tier-0", + "name": "Hobby", + "desc": "Hobby plan", + "order": 0.0, + "price": 25, + "trial": 14.0, + "bandwidth": 25.0, + "storage": 25.0, + "imageTransformations": 100.0, + "screenshotsGenerated": 50.0, + "members": 25.0, + "webhooks": 25.0, + "projects": 2.0, + "platforms": 3.0, + "users": 25.0, + "teams": 25.0, + "databases": 25.0, + "databasesReads": 500000.0, + "databasesWrites": 250000.0, + "databasesBatchSize": 100.0, + "buckets": 25.0, + "fileSize": 25.0, + "functions": 25.0, + "sites": 1.0, + "executions": 25.0, + "executionsRetentionCount": 10000.0, + "GBHours": 100.0, + "realtime": 25.0, + "realtimeMessages": 100000.0, + "messages": 1000.0, + "topics": 1.0, + "authPhone": 10.0, + "domains": 5.0, + "activityLogs": 7.0, + "usageLogs": 30.0, + "projectInactivityDays": 7.0, + "alertLimit": 80.0, + "usage": { + "bandwidth": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "executions": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "member": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "realtime": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "realtimeMessages": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "realtimeBandwidth": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "storage": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "users": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "GBHours": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "imageTransformations": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "credits": { + "name": "", + "unit": "GB", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + } + }, + "addons": { + "seats": { + "supported": True, + "planIncluded": 1.0, + "limit": 5.0, + "type": "numeric", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + }, + "projects": { + "supported": True, + "planIncluded": 1.0, + "limit": 5.0, + "type": "numeric", + "currency": "USD", + "price": 5, + "value": 25.0, + "invoiceDesc": "" + } + }, + "budgetCapEnabled": True, + "customSmtp": True, + "emailBranding": True, + "requiresPaymentMethod": True, + "requiresBillingAddress": True, + "isAvailable": True, + "selfService": True, + "premiumSupport": True, + "budgeting": True, + "supportsMockNumbers": True, + "supportsOrganizationRoles": True, + "supportsCredits": True, + "supportsDisposableEmailValidation": True, + "supportsCanonicalEmailValidation": True, + "supportsFreeEmailValidation": True, + "supportsCorporateEmailValidation": True, + "supportsProjectSpecificRoles": True, + "backupsEnabled": True, + "usagePerProject": True, + "supportedAddons": { + "baa": True, + "premiumGeoDB": True, + "premiumGeoDBOrg": True + }, + "backupPolicies": 1.0, + "deploymentSize": 30.0, + "buildSize": 2000.0, + "databasesAllowEncrypt": True, + "group": "pro" + }, + "billingEmail": "billing@org.example", + "billingStartDate": "2020-10-15T06:38:00.000+00:00", + "billingCurrentInvoiceDate": "2020-10-15T06:38:00.000+00:00", + "billingNextInvoiceDate": "2020-10-15T06:38:00.000+00:00", + "billingTrialStartDate": "2020-10-15T06:38:00.000+00:00", + "billingTrialDays": 14.0, + "billingAggregationId": "adbc3de4rddfsd", + "billingInvoiceId": "adbc3de4rddfsd", + "paymentMethodId": "adbc3de4rddfsd", + "billingAddressId": "adbc3de4rddfsd", + "backupPaymentMethodId": "adbc3de4rddfsd", + "status": "active", + "remarks": "Pending initial payment", + "agreementBAA": "", + "programManagerName": "", + "programManagerCalendar": "", + "programDiscordChannelName": "", + "programDiscordChannelUrl": "", + "billingPlanDowngrade": "tier-1", + "billingTaxId": "", + "markedForDeletion": True, + "platform": "imagine", + "projects": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.update( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_delete(self, m): + data = '' + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.delete( + ) + + self.assertEqual(response, data) + @requests_mock.Mocker() def test_list_keys(self, m): data = { @@ -109,6 +571,117 @@ def test_delete_key(self, m): self.assertEqual(response, data) + @requests_mock.Mocker() + def test_list_memberships(self, m): + data = { + "total": 5.0, + "memberships": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.list_memberships( + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_create_membership(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "userId": "5e5ea5c16897e", + "userName": "John Doe", + "userEmail": "john@appwrite.io", + "userPhone": "+1 555 555 5555", + "teamId": "5e5ea5c16897e", + "teamName": "VIP", + "invited": "2020-10-15T06:38:00.000+00:00", + "joined": "2020-10-15T06:38:00.000+00:00", + "confirm": True, + "mfa": True, + "userAccessedAt": "2020-10-15T06:38:00.000+00:00", + "roles": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.create_membership( + [], + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_membership(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "userId": "5e5ea5c16897e", + "userName": "John Doe", + "userEmail": "john@appwrite.io", + "userPhone": "+1 555 555 5555", + "teamId": "5e5ea5c16897e", + "teamName": "VIP", + "invited": "2020-10-15T06:38:00.000+00:00", + "joined": "2020-10-15T06:38:00.000+00:00", + "confirm": True, + "mfa": True, + "userAccessedAt": "2020-10-15T06:38:00.000+00:00", + "roles": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.get_membership( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_update_membership(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "userId": "5e5ea5c16897e", + "userName": "John Doe", + "userEmail": "john@appwrite.io", + "userPhone": "+1 555 555 5555", + "teamId": "5e5ea5c16897e", + "teamName": "VIP", + "invited": "2020-10-15T06:38:00.000+00:00", + "joined": "2020-10-15T06:38:00.000+00:00", + "confirm": True, + "mfa": True, + "userAccessedAt": "2020-10-15T06:38:00.000+00:00", + "roles": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.update_membership( + '', + [], + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_delete_membership(self, m): + data = '' + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.delete_membership( + '', + ) + + self.assertEqual(response, data) + @requests_mock.Mocker() def test_list_projects(self, m): data = { @@ -147,6 +720,7 @@ def test_create_project(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -187,6 +761,7 @@ def test_get_project(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -226,6 +801,7 @@ def test_update_project(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], diff --git a/test/services/test_project.py b/test/services/test_project.py index 9440e29e..c3e87cf3 100644 --- a/test/services/test_project.py +++ b/test/services/test_project.py @@ -37,6 +37,7 @@ def test_get(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -86,6 +87,7 @@ def test_update_auth_method(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -245,6 +247,7 @@ def test_update_labels(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -377,6 +380,7 @@ def test_update_o_auth2_server(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -427,6 +431,22 @@ def test_update_o_auth2_apple(self, m): self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() + def test_update_o_auth2_appwrite(self, m): + data = { + "$id": "github", + "enabled": True, + "clientId": "6a42000000000000b5a0", + "clientSecret": "b86afd000000000000000000000000000000000000000000000000000ced5f93" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.project.update_o_auth2_appwrite( + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_update_o_auth2_auth0(self, m): data = { @@ -797,7 +817,8 @@ def test_update_o_auth2_oidc(self, m): "wellKnownURL": "https:\/\/myoauth.com\/.well-known\/openid-configuration", "authorizationURL": "https:\/\/myoauth.com\/oauth2\/authorize", "tokenURL": "https:\/\/myoauth.com\/oauth2\/token", - "userInfoURL": "https:\/\/myoauth.com\/oauth2\/userinfo" + "userInfoURL": "https:\/\/myoauth.com\/oauth2\/userinfo", + "prompt": [] } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1391,6 +1412,7 @@ def test_update_deny_aliased_email_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1430,6 +1452,7 @@ def test_update_deny_corporate_email_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1469,6 +1492,7 @@ def test_update_deny_disposable_email_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1508,6 +1532,7 @@ def test_update_deny_free_email_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1547,6 +1572,7 @@ def test_update_membership_privacy_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1585,6 +1611,7 @@ def test_update_password_dictionary_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1624,6 +1651,7 @@ def test_update_password_history_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1663,6 +1691,7 @@ def test_update_password_personal_data_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1720,6 +1749,7 @@ def test_update_session_alert_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1759,6 +1789,7 @@ def test_update_session_duration_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1798,6 +1829,7 @@ def test_update_session_invalidation_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1837,6 +1869,7 @@ def test_update_session_limit_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1876,6 +1909,7 @@ def test_update_user_limit_policy(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1930,6 +1964,7 @@ def test_update_protocol(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -1970,6 +2005,7 @@ def test_update_service(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], @@ -2010,6 +2046,7 @@ def test_update_smtp(self, m): "pingedAt": "2020-10-15T06:38:00.000+00:00", "labels": [], "status": "active", + "onboarding": {}, "authMethods": [], "services": [], "protocols": [], diff --git a/test/services/test_usage.py b/test/services/test_usage.py deleted file mode 100644 index d54ff32f..00000000 --- a/test/services/test_usage.py +++ /dev/null @@ -1,45 +0,0 @@ -import json -import requests_mock -import unittest - -from appwrite.client import Client -from appwrite.input_file import InputFile -from appwrite.models import * -from appwrite.services.usage import Usage - -class UsageServiceTest(unittest.TestCase): - - def setUp(self): - self.client = Client() - self.usage = Usage(self.client) - - @requests_mock.Mocker() - def test_list_events(self, m): - data = { - "interval": "1d", - "metrics": [] -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.usage.list_events( - [], - ) - - self.assertEqual(response.to_dict(), data) - - @requests_mock.Mocker() - def test_list_gauges(self, m): - data = { - "interval": "1d", - "metrics": [] -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.usage.list_gauges( - [], - ) - - self.assertEqual(response.to_dict(), data) - From 0f3c9d2a792f2066c689d4581b0965139b39b188 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 13 Jul 2026 12:11:04 +0530 Subject: [PATCH 2/8] chore: update Python SDK to 22.0.0 --- appwrite/client.py | 2 +- appwrite/services/backups.py | 15 +- appwrite/services/databases.py | 207 ++++++++---- appwrite/services/functions.py | 24 +- appwrite/services/messaging.py | 216 ++++++++---- appwrite/services/oauth2.py | 3 +- appwrite/services/organization.py | 6 +- appwrite/services/project.py | 525 ++++++++++++++++++++---------- appwrite/services/sites.py | 18 +- appwrite/services/storage.py | 9 +- appwrite/services/tables_db.py | 210 ++++++++---- appwrite/services/tokens.py | 6 +- appwrite/services/users.py | 6 +- appwrite/services/webhooks.py | 6 +- 14 files changed, 835 insertions(+), 418 deletions(-) diff --git a/appwrite/client.py b/appwrite/client.py index 83f61a8f..0fc438b7 100644 --- a/appwrite/client.py +++ b/appwrite/client.py @@ -70,7 +70,7 @@ def set_jwt(self, value): def set_bearer(self, value): """The OAuth access token to authenticate with""" - self._global_headers['authorization'] = value + self._global_headers['authorization'] = 'Bearer ' + value self._config['bearer'] = value return self diff --git a/appwrite/services/backups.py b/appwrite/services/backups.py index fda14881..5aac7329 100644 --- a/appwrite/services/backups.py +++ b/appwrite/services/backups.py @@ -86,7 +86,8 @@ def create_archive( api_params['services'] = self._normalize_value(services) - api_params['resourceId'] = self._normalize_value(resource_id) + if resource_id is not None: + api_params['resourceId'] = self._normalize_value(resource_id) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -273,7 +274,8 @@ def create_policy( if name is not None: api_params['name'] = self._normalize_value(name) api_params['services'] = self._normalize_value(services) - api_params['resourceId'] = self._normalize_value(resource_id) + if resource_id is not None: + api_params['resourceId'] = self._normalize_value(resource_id) if enabled is not None: api_params['enabled'] = self._normalize_value(enabled) api_params['retention'] = self._normalize_value(retention) @@ -369,11 +371,14 @@ def update_policy( api_path = api_path.replace('{policyId}', str(self._normalize_value(policy_id))) - api_params['name'] = self._normalize_value(name) - api_params['retention'] = self._normalize_value(retention) + if name is not None: + api_params['name'] = self._normalize_value(name) + if retention is not None: + api_params['retention'] = self._normalize_value(retention) if schedule is not None: api_params['schedule'] = self._normalize_value(schedule) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/databases.py b/appwrite/services/databases.py index fdc30428..51831aec 100644 --- a/appwrite/services/databases.py +++ b/appwrite/services/databases.py @@ -672,7 +672,8 @@ def create_collection( api_params['collectionId'] = self._normalize_value(collection_id) api_params['name'] = self._normalize_value(name) - api_params['permissions'] = self._normalize_value(permissions) + if permissions is not None: + api_params['permissions'] = self._normalize_value(permissions) if document_security is not None: api_params['documentSecurity'] = self._normalize_value(document_security) if enabled is not None: @@ -797,7 +798,8 @@ def update_collection( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['permissions'] = self._normalize_value(permissions) + if permissions is not None: + api_params['permissions'] = self._normalize_value(permissions) if document_security is not None: api_params['documentSecurity'] = self._normalize_value(document_security) if enabled is not None: @@ -989,9 +991,12 @@ def create_big_int_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['min'] = self._normalize_value(min) - api_params['max'] = self._normalize_value(max) - api_params['default'] = self._normalize_value(default) + if min is not None: + api_params['min'] = self._normalize_value(min) + if max is not None: + api_params['max'] = self._normalize_value(max) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1071,10 +1076,13 @@ def update_big_int_attribute( api_path = api_path.replace('{key}', str(self._normalize_value(key))) api_params['required'] = self._normalize_value(required) - api_params['min'] = self._normalize_value(min) - api_params['max'] = self._normalize_value(max) + if min is not None: + api_params['min'] = self._normalize_value(min) + if max is not None: + api_params['max'] = self._normalize_value(max) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1146,7 +1154,8 @@ def create_boolean_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1220,7 +1229,8 @@ def update_boolean_attribute( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1291,7 +1301,8 @@ def create_datetime_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1365,7 +1376,8 @@ def update_datetime_attribute( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1437,7 +1449,8 @@ def create_email_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1512,7 +1525,8 @@ def update_email_attribute( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1591,7 +1605,8 @@ def create_enum_attribute( api_params['key'] = self._normalize_value(key) api_params['elements'] = self._normalize_value(elements) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1673,7 +1688,8 @@ def update_enum_attribute( api_params['elements'] = self._normalize_value(elements) api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1751,9 +1767,12 @@ def create_float_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['min'] = self._normalize_value(min) - api_params['max'] = self._normalize_value(max) - api_params['default'] = self._normalize_value(default) + if min is not None: + api_params['min'] = self._normalize_value(min) + if max is not None: + api_params['max'] = self._normalize_value(max) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1833,10 +1852,13 @@ def update_float_attribute( api_path = api_path.replace('{key}', str(self._normalize_value(key))) api_params['required'] = self._normalize_value(required) - api_params['min'] = self._normalize_value(min) - api_params['max'] = self._normalize_value(max) + if min is not None: + api_params['min'] = self._normalize_value(min) + if max is not None: + api_params['max'] = self._normalize_value(max) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1914,9 +1936,12 @@ def create_integer_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['min'] = self._normalize_value(min) - api_params['max'] = self._normalize_value(max) - api_params['default'] = self._normalize_value(default) + if min is not None: + api_params['min'] = self._normalize_value(min) + if max is not None: + api_params['max'] = self._normalize_value(max) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1996,10 +2021,13 @@ def update_integer_attribute( api_path = api_path.replace('{key}', str(self._normalize_value(key))) api_params['required'] = self._normalize_value(required) - api_params['min'] = self._normalize_value(min) - api_params['max'] = self._normalize_value(max) + if min is not None: + api_params['min'] = self._normalize_value(min) + if max is not None: + api_params['max'] = self._normalize_value(max) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2071,7 +2099,8 @@ def create_ip_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -2146,7 +2175,8 @@ def update_ip_attribute( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2214,7 +2244,8 @@ def create_line_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2285,8 +2316,10 @@ def update_line_attribute( api_path = api_path.replace('{key}', str(self._normalize_value(key))) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if default is not None: + api_params['default'] = self._normalize_value(default) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2361,7 +2394,8 @@ def create_longtext_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) if encrypt is not None: @@ -2438,7 +2472,8 @@ def update_longtext_attribute( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2513,7 +2548,8 @@ def create_mediumtext_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) if encrypt is not None: @@ -2590,7 +2626,8 @@ def update_mediumtext_attribute( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2658,7 +2695,8 @@ def create_point_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2729,8 +2767,10 @@ def update_point_attribute( api_path = api_path.replace('{key}', str(self._normalize_value(key))) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if default is not None: + api_params['default'] = self._normalize_value(default) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2798,7 +2838,8 @@ def create_polygon_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2869,8 +2910,10 @@ def update_polygon_attribute( api_path = api_path.replace('{key}', str(self._normalize_value(key))) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if default is not None: + api_params['default'] = self._normalize_value(default) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2950,8 +2993,10 @@ def create_relationship_attribute( api_params['type'] = self._normalize_value(type) if two_way is not None: api_params['twoWay'] = self._normalize_value(two_way) - api_params['key'] = self._normalize_value(key) - api_params['twoWayKey'] = self._normalize_value(two_way_key) + if key is not None: + api_params['key'] = self._normalize_value(key) + if two_way_key is not None: + api_params['twoWayKey'] = self._normalize_value(two_way_key) if on_delete is not None: api_params['onDelete'] = self._normalize_value(on_delete) @@ -3020,7 +3065,8 @@ def update_relationship_attribute( if on_delete is not None: api_params['onDelete'] = self._normalize_value(on_delete) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -3102,7 +3148,8 @@ def create_string_attribute( api_params['key'] = self._normalize_value(key) api_params['size'] = self._normalize_value(size) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) if encrypt is not None: @@ -3182,8 +3229,10 @@ def update_string_attribute( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['size'] = self._normalize_value(size) - api_params['newKey'] = self._normalize_value(new_key) + if size is not None: + api_params['size'] = self._normalize_value(size) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -3258,7 +3307,8 @@ def create_text_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) if encrypt is not None: @@ -3335,7 +3385,8 @@ def update_text_attribute( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -3407,7 +3458,8 @@ def create_url_attribute( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -3482,7 +3534,8 @@ def update_url_attribute( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -3564,7 +3617,8 @@ def create_varchar_attribute( api_params['key'] = self._normalize_value(key) api_params['size'] = self._normalize_value(size) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) if encrypt is not None: @@ -3644,8 +3698,10 @@ def update_varchar_attribute( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['size'] = self._normalize_value(size) - api_params['newKey'] = self._normalize_value(new_key) + if size is not None: + api_params['size'] = self._normalize_value(size) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -3937,8 +3993,10 @@ def create_document( api_params['documentId'] = self._normalize_value(document_id) api_params['data'] = self._normalize_value(data) - api_params['permissions'] = self._normalize_value(permissions) - api_params['transactionId'] = self._normalize_value(transaction_id) + 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', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4003,7 +4061,8 @@ def create_documents( api_path = api_path.replace('{collectionId}', str(self._normalize_value(collection_id))) api_params['documents'] = self._normalize_value(documents) - api_params['transactionId'] = self._normalize_value(transaction_id) + if transaction_id is not None: + api_params['transactionId'] = self._normalize_value(transaction_id) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4069,7 +4128,8 @@ def upsert_documents( api_path = api_path.replace('{collectionId}', str(self._normalize_value(collection_id))) api_params['documents'] = self._normalize_value(documents) - api_params['transactionId'] = self._normalize_value(transaction_id) + if transaction_id is not None: + api_params['transactionId'] = self._normalize_value(transaction_id) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4137,7 +4197,8 @@ def update_documents( api_params['data'] = self._normalize_value(data) if queries is not None: api_params['queries'] = self._normalize_value(queries) - api_params['transactionId'] = self._normalize_value(transaction_id) + if transaction_id is not None: + api_params['transactionId'] = self._normalize_value(transaction_id) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4344,8 +4405,10 @@ def upsert_document( if data is not None: api_params['data'] = self._normalize_value(data) - api_params['permissions'] = self._normalize_value(permissions) - api_params['transactionId'] = self._normalize_value(transaction_id) + 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('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4418,8 +4481,10 @@ def update_document( if data is not None: api_params['data'] = self._normalize_value(data) - api_params['permissions'] = self._normalize_value(permissions) - api_params['transactionId'] = self._normalize_value(transaction_id) + 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('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4560,8 +4625,10 @@ def decrement_document_attribute( if value is not None: api_params['value'] = self._normalize_value(value) - api_params['min'] = self._normalize_value(min) - api_params['transactionId'] = self._normalize_value(transaction_id) + if min is not None: + api_params['min'] = self._normalize_value(min) + if transaction_id is not None: + api_params['transactionId'] = self._normalize_value(transaction_id) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4641,8 +4708,10 @@ def increment_document_attribute( if value is not None: api_params['value'] = self._normalize_value(value) - api_params['max'] = self._normalize_value(max) - api_params['transactionId'] = self._normalize_value(transaction_id) + if max is not None: + api_params['max'] = self._normalize_value(max) + if transaction_id is not None: + api_params['transactionId'] = self._normalize_value(transaction_id) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/functions.py b/appwrite/services/functions.py index 18709fb2..4b0f552f 100644 --- a/appwrite/services/functions.py +++ b/appwrite/services/functions.py @@ -445,16 +445,20 @@ def update( api_params['scopes'] = self._normalize_value(scopes) if installation_id is not None: api_params['installationId'] = self._normalize_value(installation_id) - api_params['providerRepositoryId'] = self._normalize_value(provider_repository_id) + if provider_repository_id is not None: + api_params['providerRepositoryId'] = self._normalize_value(provider_repository_id) if provider_branch is not None: api_params['providerBranch'] = self._normalize_value(provider_branch) if provider_silent_mode is not None: api_params['providerSilentMode'] = self._normalize_value(provider_silent_mode) if provider_root_directory is not None: api_params['providerRootDirectory'] = self._normalize_value(provider_root_directory) - api_params['providerBranches'] = self._normalize_value(provider_branches) - api_params['providerPaths'] = self._normalize_value(provider_paths) - api_params['buildSpecification'] = self._normalize_value(build_specification) + if provider_branches is not None: + api_params['providerBranches'] = self._normalize_value(provider_branches) + if provider_paths is not None: + api_params['providerPaths'] = self._normalize_value(provider_paths) + if build_specification is not None: + api_params['buildSpecification'] = self._normalize_value(build_specification) if runtime_specification is not None: api_params['runtimeSpecification'] = self._normalize_value(runtime_specification) if deployment_retention is not None: @@ -1182,7 +1186,8 @@ def create_execution( api_params['method'] = self._normalize_value(method) if headers is not None: api_params['headers'] = self._normalize_value(headers) - api_params['scheduledAt'] = self._normalize_value(scheduled_at) + if scheduled_at is not None: + api_params['scheduledAt'] = self._normalize_value(scheduled_at) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1492,9 +1497,12 @@ def update_variable( api_path = api_path.replace('{functionId}', str(self._normalize_value(function_id))) api_path = api_path.replace('{variableId}', str(self._normalize_value(variable_id))) - api_params['key'] = self._normalize_value(key) - api_params['value'] = self._normalize_value(value) - api_params['secret'] = self._normalize_value(secret) + if key is not None: + api_params['key'] = self._normalize_value(key) + if value is not None: + api_params['value'] = self._normalize_value(value) + if secret is not None: + api_params['secret'] = self._normalize_value(secret) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/messaging.py b/appwrite/services/messaging.py index 3abb1004..2ebe2bd6 100644 --- a/appwrite/services/messaging.py +++ b/appwrite/services/messaging.py @@ -154,7 +154,8 @@ def create_email( api_params['draft'] = self._normalize_value(draft) if html is not None: api_params['html'] = self._normalize_value(html) - api_params['scheduledAt'] = self._normalize_value(scheduled_at) + if scheduled_at is not None: + api_params['scheduledAt'] = self._normalize_value(scheduled_at) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -229,17 +230,28 @@ def update_email( api_path = api_path.replace('{messageId}', str(self._normalize_value(message_id))) - api_params['topics'] = self._normalize_value(topics) - api_params['users'] = self._normalize_value(users) - api_params['targets'] = self._normalize_value(targets) - api_params['subject'] = self._normalize_value(subject) - api_params['content'] = self._normalize_value(content) - api_params['draft'] = self._normalize_value(draft) - api_params['html'] = self._normalize_value(html) - api_params['cc'] = self._normalize_value(cc) - api_params['bcc'] = self._normalize_value(bcc) - api_params['scheduledAt'] = self._normalize_value(scheduled_at) - api_params['attachments'] = self._normalize_value(attachments) + if topics is not None: + api_params['topics'] = self._normalize_value(topics) + if users is not None: + api_params['users'] = self._normalize_value(users) + if targets is not None: + api_params['targets'] = self._normalize_value(targets) + if subject is not None: + api_params['subject'] = self._normalize_value(subject) + if content is not None: + api_params['content'] = self._normalize_value(content) + if draft is not None: + api_params['draft'] = self._normalize_value(draft) + if html is not None: + api_params['html'] = self._normalize_value(html) + if cc is not None: + api_params['cc'] = self._normalize_value(cc) + if bcc is not None: + api_params['bcc'] = self._normalize_value(bcc) + if scheduled_at is not None: + api_params['scheduledAt'] = self._normalize_value(scheduled_at) + if attachments is not None: + api_params['attachments'] = self._normalize_value(attachments) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -344,7 +356,8 @@ def create_push( api_params['users'] = self._normalize_value(users) if targets is not None: api_params['targets'] = self._normalize_value(targets) - api_params['data'] = self._normalize_value(data) + if data is not None: + api_params['data'] = self._normalize_value(data) if action is not None: api_params['action'] = self._normalize_value(action) if image is not None: @@ -361,7 +374,8 @@ def create_push( api_params['badge'] = self._normalize_value(badge) if draft is not None: api_params['draft'] = self._normalize_value(draft) - api_params['scheduledAt'] = self._normalize_value(scheduled_at) + if scheduled_at is not None: + api_params['scheduledAt'] = self._normalize_value(scheduled_at) if content_available is not None: api_params['contentAvailable'] = self._normalize_value(content_available) if critical is not None: @@ -463,24 +477,42 @@ def update_push( api_path = api_path.replace('{messageId}', str(self._normalize_value(message_id))) - api_params['topics'] = self._normalize_value(topics) - api_params['users'] = self._normalize_value(users) - api_params['targets'] = self._normalize_value(targets) - api_params['title'] = self._normalize_value(title) - api_params['body'] = self._normalize_value(body) - api_params['data'] = self._normalize_value(data) - api_params['action'] = self._normalize_value(action) - api_params['image'] = self._normalize_value(image) - api_params['icon'] = self._normalize_value(icon) - api_params['sound'] = self._normalize_value(sound) - api_params['color'] = self._normalize_value(color) - api_params['tag'] = self._normalize_value(tag) - api_params['badge'] = self._normalize_value(badge) - api_params['draft'] = self._normalize_value(draft) - api_params['scheduledAt'] = self._normalize_value(scheduled_at) - api_params['contentAvailable'] = self._normalize_value(content_available) - api_params['critical'] = self._normalize_value(critical) - api_params['priority'] = self._normalize_value(priority) + if topics is not None: + api_params['topics'] = self._normalize_value(topics) + if users is not None: + api_params['users'] = self._normalize_value(users) + if targets is not None: + api_params['targets'] = self._normalize_value(targets) + if title is not None: + api_params['title'] = self._normalize_value(title) + if body is not None: + api_params['body'] = self._normalize_value(body) + if data is not None: + api_params['data'] = self._normalize_value(data) + if action is not None: + api_params['action'] = self._normalize_value(action) + if image is not None: + api_params['image'] = self._normalize_value(image) + if icon is not None: + api_params['icon'] = self._normalize_value(icon) + if sound is not None: + api_params['sound'] = self._normalize_value(sound) + if color is not None: + api_params['color'] = self._normalize_value(color) + if tag is not None: + api_params['tag'] = self._normalize_value(tag) + if badge is not None: + api_params['badge'] = self._normalize_value(badge) + if draft is not None: + api_params['draft'] = self._normalize_value(draft) + if scheduled_at is not None: + api_params['scheduledAt'] = self._normalize_value(scheduled_at) + if content_available is not None: + api_params['contentAvailable'] = self._normalize_value(content_available) + if critical is not None: + api_params['critical'] = self._normalize_value(critical) + if priority is not None: + api_params['priority'] = self._normalize_value(priority) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -551,7 +583,8 @@ def create_sms( api_params['targets'] = self._normalize_value(targets) if draft is not None: api_params['draft'] = self._normalize_value(draft) - api_params['scheduledAt'] = self._normalize_value(scheduled_at) + if scheduled_at is not None: + api_params['scheduledAt'] = self._normalize_value(scheduled_at) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -611,12 +644,18 @@ def update_sms( api_path = api_path.replace('{messageId}', str(self._normalize_value(message_id))) - api_params['topics'] = self._normalize_value(topics) - api_params['users'] = self._normalize_value(users) - api_params['targets'] = self._normalize_value(targets) - api_params['content'] = self._normalize_value(content) - api_params['draft'] = self._normalize_value(draft) - api_params['scheduledAt'] = self._normalize_value(scheduled_at) + if topics is not None: + api_params['topics'] = self._normalize_value(topics) + if users is not None: + api_params['users'] = self._normalize_value(users) + if targets is not None: + api_params['targets'] = self._normalize_value(targets) + if content is not None: + api_params['content'] = self._normalize_value(content) + if draft is not None: + api_params['draft'] = self._normalize_value(draft) + if scheduled_at is not None: + api_params['scheduledAt'] = self._normalize_value(scheduled_at) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -867,7 +906,8 @@ def create_apns_provider( api_params['bundleId'] = self._normalize_value(bundle_id) if sandbox is not None: api_params['sandbox'] = self._normalize_value(sandbox) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -931,7 +971,8 @@ def update_apns_provider( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) if auth_key is not None: api_params['authKey'] = self._normalize_value(auth_key) if auth_key_id is not None: @@ -940,7 +981,8 @@ def update_apns_provider( api_params['teamId'] = self._normalize_value(team_id) if bundle_id is not None: api_params['bundleId'] = self._normalize_value(bundle_id) - api_params['sandbox'] = self._normalize_value(sandbox) + if sandbox is not None: + api_params['sandbox'] = self._normalize_value(sandbox) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -994,8 +1036,10 @@ def create_fcm_provider( api_params['providerId'] = self._normalize_value(provider_id) api_params['name'] = self._normalize_value(name) - api_params['serviceAccountJSON'] = self._normalize_value(service_account_json) - api_params['enabled'] = self._normalize_value(enabled) + if service_account_json is not None: + api_params['serviceAccountJSON'] = self._normalize_value(service_account_json) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1047,8 +1091,10 @@ def update_fcm_provider( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['enabled'] = self._normalize_value(enabled) - api_params['serviceAccountJSON'] = self._normalize_value(service_account_json) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) + if service_account_json is not None: + api_params['serviceAccountJSON'] = self._normalize_value(service_account_json) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1124,7 +1170,8 @@ def create_mailgun_provider( api_params['apiKey'] = self._normalize_value(api_key) if domain is not None: api_params['domain'] = self._normalize_value(domain) - api_params['isEuRegion'] = self._normalize_value(is_eu_region) + if is_eu_region is not None: + api_params['isEuRegion'] = self._normalize_value(is_eu_region) if from_name is not None: api_params['fromName'] = self._normalize_value(from_name) if from_email is not None: @@ -1133,7 +1180,8 @@ def create_mailgun_provider( api_params['replyToName'] = self._normalize_value(reply_to_name) if reply_to_email is not None: api_params['replyToEmail'] = self._normalize_value(reply_to_email) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1207,8 +1255,10 @@ def update_mailgun_provider( api_params['apiKey'] = self._normalize_value(api_key) if domain is not None: api_params['domain'] = self._normalize_value(domain) - api_params['isEuRegion'] = self._normalize_value(is_eu_region) - api_params['enabled'] = self._normalize_value(enabled) + if is_eu_region is not None: + api_params['isEuRegion'] = self._normalize_value(is_eu_region) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) if from_name is not None: api_params['fromName'] = self._normalize_value(from_name) if from_email is not None: @@ -1282,7 +1332,8 @@ def create_msg91_provider( api_params['senderId'] = self._normalize_value(sender_id) if auth_key is not None: api_params['authKey'] = self._normalize_value(auth_key) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1340,7 +1391,8 @@ def update_msg91_provider( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) if template_id is not None: api_params['templateId'] = self._normalize_value(template_id) if sender_id is not None: @@ -1422,7 +1474,8 @@ def create_resend_provider( api_params['replyToName'] = self._normalize_value(reply_to_name) if reply_to_email is not None: api_params['replyToEmail'] = self._normalize_value(reply_to_email) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1486,7 +1539,8 @@ def update_resend_provider( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) if api_key is not None: api_params['apiKey'] = self._normalize_value(api_key) if from_name is not None: @@ -1572,7 +1626,8 @@ def create_sendgrid_provider( api_params['replyToName'] = self._normalize_value(reply_to_name) if reply_to_email is not None: api_params['replyToEmail'] = self._normalize_value(reply_to_email) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1636,7 +1691,8 @@ def update_sendgrid_provider( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) if api_key is not None: api_params['apiKey'] = self._normalize_value(api_key) if from_name is not None: @@ -1732,7 +1788,8 @@ def create_ses_provider( api_params['replyToName'] = self._normalize_value(reply_to_name) if reply_to_email is not None: api_params['replyToEmail'] = self._normalize_value(reply_to_email) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1802,7 +1859,8 @@ def update_ses_provider( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) if access_key is not None: api_params['accessKey'] = self._normalize_value(access_key) if secret_key is not None: @@ -1924,7 +1982,8 @@ def create_smtp_provider( api_params['replyToName'] = self._normalize_value(reply_to_name) if reply_to_email is not None: api_params['replyToEmail'] = self._normalize_value(reply_to_email) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2008,14 +2067,16 @@ def update_smtp_provider( api_params['name'] = self._normalize_value(name) if host is not None: api_params['host'] = self._normalize_value(host) - api_params['port'] = self._normalize_value(port) + if port is not None: + api_params['port'] = self._normalize_value(port) if username is not None: api_params['username'] = self._normalize_value(username) if password is not None: api_params['password'] = self._normalize_value(password) if encryption is not None: api_params['encryption'] = self._normalize_value(encryption) - api_params['autoTLS'] = self._normalize_value(auto_tls) + if auto_tls is not None: + api_params['autoTLS'] = self._normalize_value(auto_tls) if mailer is not None: api_params['mailer'] = self._normalize_value(mailer) if from_name is not None: @@ -2026,7 +2087,8 @@ def update_smtp_provider( api_params['replyToName'] = self._normalize_value(reply_to_name) if reply_to_email is not None: api_params['replyToEmail'] = self._normalize_value(reply_to_email) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2092,7 +2154,8 @@ def create_telesign_provider( api_params['customerId'] = self._normalize_value(customer_id) if api_key is not None: api_params['apiKey'] = self._normalize_value(api_key) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2150,7 +2213,8 @@ def update_telesign_provider( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) if customer_id is not None: api_params['customerId'] = self._normalize_value(customer_id) if api_key is not None: @@ -2222,7 +2286,8 @@ def create_textmagic_provider( api_params['username'] = self._normalize_value(username) if api_key is not None: api_params['apiKey'] = self._normalize_value(api_key) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2280,7 +2345,8 @@ def update_textmagic_provider( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) if username is not None: api_params['username'] = self._normalize_value(username) if api_key is not None: @@ -2352,7 +2418,8 @@ def create_twilio_provider( api_params['accountSid'] = self._normalize_value(account_sid) if auth_token is not None: api_params['authToken'] = self._normalize_value(auth_token) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2410,7 +2477,8 @@ def update_twilio_provider( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) if account_sid is not None: api_params['accountSid'] = self._normalize_value(account_sid) if auth_token is not None: @@ -2482,7 +2550,8 @@ def create_vonage_provider( api_params['apiKey'] = self._normalize_value(api_key) if api_secret is not None: api_params['apiSecret'] = self._normalize_value(api_secret) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2540,7 +2609,8 @@ def update_vonage_provider( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['enabled'] = self._normalize_value(enabled) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) if api_key is not None: api_params['apiKey'] = self._normalize_value(api_key) if api_secret is not None: @@ -2812,8 +2882,10 @@ def update_topic( api_path = api_path.replace('{topicId}', str(self._normalize_value(topic_id))) - api_params['name'] = self._normalize_value(name) - api_params['subscribe'] = self._normalize_value(subscribe) + if name is not None: + api_params['name'] = self._normalize_value(name) + if subscribe is not None: + api_params['subscribe'] = self._normalize_value(subscribe) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/oauth2.py b/appwrite/services/oauth2.py index 272fbc73..21612207 100644 --- a/appwrite/services/oauth2.py +++ b/appwrite/services/oauth2.py @@ -441,7 +441,8 @@ def create_par( api_params['code_challenge_method'] = self._normalize_value(code_challenge_method) if prompt is not None: api_params['prompt'] = self._normalize_value(prompt) - api_params['max_age'] = self._normalize_value(max_age) + if max_age is not None: + api_params['max_age'] = self._normalize_value(max_age) if authorization_details is not None: api_params['authorization_details'] = self._normalize_value(authorization_details) if resource is not None: diff --git a/appwrite/services/organization.py b/appwrite/services/organization.py index eaf0ca88..e3f11e50 100644 --- a/appwrite/services/organization.py +++ b/appwrite/services/organization.py @@ -216,7 +216,8 @@ def create_key( api_params['keyId'] = self._normalize_value(key_id) api_params['name'] = self._normalize_value(name) api_params['scopes'] = self._normalize_value(scopes) - api_params['expire'] = self._normalize_value(expire) + if expire is not None: + api_params['expire'] = self._normalize_value(expire) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -313,7 +314,8 @@ def update_key( api_params['name'] = self._normalize_value(name) api_params['scopes'] = self._normalize_value(scopes) - api_params['expire'] = self._normalize_value(expire) + if expire is not None: + api_params['expire'] = self._normalize_value(expire) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/project.py b/appwrite/services/project.py index c9d5c9d4..573abdae 100644 --- a/appwrite/services/project.py +++ b/appwrite/services/project.py @@ -286,7 +286,8 @@ def create_key( api_params['keyId'] = self._normalize_value(key_id) api_params['name'] = self._normalize_value(name) api_params['scopes'] = self._normalize_value(scopes) - api_params['expire'] = self._normalize_value(expire) + if expire is not None: + api_params['expire'] = self._normalize_value(expire) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -432,7 +433,8 @@ def update_key( api_params['name'] = self._normalize_value(name) api_params['scopes'] = self._normalize_value(scopes) - api_params['expire'] = self._normalize_value(expire) + if expire is not None: + api_params['expire'] = self._normalize_value(expire) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -855,17 +857,24 @@ def update_o_auth2_server( api_params['scopes'] = self._normalize_value(scopes) if authorization_details_types is not None: api_params['authorizationDetailsTypes'] = self._normalize_value(authorization_details_types) - api_params['accessTokenDuration'] = self._normalize_value(access_token_duration) - api_params['refreshTokenDuration'] = self._normalize_value(refresh_token_duration) - api_params['publicAccessTokenDuration'] = self._normalize_value(public_access_token_duration) - api_params['publicRefreshTokenDuration'] = self._normalize_value(public_refresh_token_duration) - api_params['confidentialPkce'] = self._normalize_value(confidential_pkce) + if access_token_duration is not None: + api_params['accessTokenDuration'] = self._normalize_value(access_token_duration) + if refresh_token_duration is not None: + api_params['refreshTokenDuration'] = self._normalize_value(refresh_token_duration) + if public_access_token_duration is not None: + api_params['publicAccessTokenDuration'] = self._normalize_value(public_access_token_duration) + if public_refresh_token_duration is not None: + api_params['publicRefreshTokenDuration'] = self._normalize_value(public_refresh_token_duration) + if confidential_pkce is not None: + api_params['confidentialPkce'] = self._normalize_value(confidential_pkce) if verification_url is not None: api_params['verificationUrl'] = self._normalize_value(verification_url) - api_params['userCodeLength'] = self._normalize_value(user_code_length) + if user_code_length is not None: + api_params['userCodeLength'] = self._normalize_value(user_code_length) if user_code_format is not None: api_params['userCodeFormat'] = self._normalize_value(user_code_format) - api_params['deviceCodeDuration'] = self._normalize_value(device_code_duration) + if device_code_duration is not None: + api_params['deviceCodeDuration'] = self._normalize_value(device_code_duration) if default_scopes is not None: api_params['defaultScopes'] = self._normalize_value(default_scopes) @@ -910,9 +919,12 @@ def update_o_auth2_amazon( api_path = '/project/oauth2/amazon' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -961,11 +973,16 @@ def update_o_auth2_apple( api_path = '/project/oauth2/apple' api_params = {} - api_params['serviceId'] = self._normalize_value(service_id) - api_params['keyId'] = self._normalize_value(key_id) - api_params['teamId'] = self._normalize_value(team_id) - api_params['p8File'] = self._normalize_value(p8_file) - api_params['enabled'] = self._normalize_value(enabled) + if service_id is not None: + api_params['serviceId'] = self._normalize_value(service_id) + if key_id is not None: + api_params['keyId'] = self._normalize_value(key_id) + if team_id is not None: + api_params['teamId'] = self._normalize_value(team_id) + if p8_file is not None: + api_params['p8File'] = self._normalize_value(p8_file) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1008,9 +1025,12 @@ def update_o_auth2_appwrite( api_path = '/project/oauth2/appwrite' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1056,10 +1076,14 @@ def update_o_auth2_auth0( api_path = '/project/oauth2/auth0' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['endpoint'] = self._normalize_value(endpoint) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if endpoint is not None: + api_params['endpoint'] = self._normalize_value(endpoint) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1105,10 +1129,14 @@ def update_o_auth2_authentik( api_path = '/project/oauth2/authentik' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['endpoint'] = self._normalize_value(endpoint) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if endpoint is not None: + api_params['endpoint'] = self._normalize_value(endpoint) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1151,9 +1179,12 @@ def update_o_auth2_autodesk( api_path = '/project/oauth2/autodesk' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1196,9 +1227,12 @@ def update_o_auth2_bitbucket( api_path = '/project/oauth2/bitbucket' api_params = {} - api_params['key'] = self._normalize_value(key) - api_params['secret'] = self._normalize_value(secret) - api_params['enabled'] = self._normalize_value(enabled) + if key is not None: + api_params['key'] = self._normalize_value(key) + if secret is not None: + api_params['secret'] = self._normalize_value(secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1241,9 +1275,12 @@ def update_o_auth2_bitly( api_path = '/project/oauth2/bitly' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1286,9 +1323,12 @@ def update_o_auth2_box( api_path = '/project/oauth2/box' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1331,9 +1371,12 @@ def update_o_auth2_dailymotion( api_path = '/project/oauth2/dailymotion' api_params = {} - api_params['apiKey'] = self._normalize_value(api_key) - api_params['apiSecret'] = self._normalize_value(api_secret) - api_params['enabled'] = self._normalize_value(enabled) + if api_key is not None: + api_params['apiKey'] = self._normalize_value(api_key) + if api_secret is not None: + api_params['apiSecret'] = self._normalize_value(api_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1376,9 +1419,12 @@ def update_o_auth2_discord( api_path = '/project/oauth2/discord' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1421,9 +1467,12 @@ def update_o_auth2_disqus( api_path = '/project/oauth2/disqus' api_params = {} - api_params['publicKey'] = self._normalize_value(public_key) - api_params['secretKey'] = self._normalize_value(secret_key) - api_params['enabled'] = self._normalize_value(enabled) + if public_key is not None: + api_params['publicKey'] = self._normalize_value(public_key) + if secret_key is not None: + api_params['secretKey'] = self._normalize_value(secret_key) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1466,9 +1515,12 @@ def update_o_auth2_dropbox( api_path = '/project/oauth2/dropbox' api_params = {} - api_params['appKey'] = self._normalize_value(app_key) - api_params['appSecret'] = self._normalize_value(app_secret) - api_params['enabled'] = self._normalize_value(enabled) + if app_key is not None: + api_params['appKey'] = self._normalize_value(app_key) + if app_secret is not None: + api_params['appSecret'] = self._normalize_value(app_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1511,9 +1563,12 @@ def update_o_auth2_etsy( api_path = '/project/oauth2/etsy' api_params = {} - api_params['keyString'] = self._normalize_value(key_string) - api_params['sharedSecret'] = self._normalize_value(shared_secret) - api_params['enabled'] = self._normalize_value(enabled) + if key_string is not None: + api_params['keyString'] = self._normalize_value(key_string) + if shared_secret is not None: + api_params['sharedSecret'] = self._normalize_value(shared_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1556,9 +1611,12 @@ def update_o_auth2_facebook( api_path = '/project/oauth2/facebook' api_params = {} - api_params['appId'] = self._normalize_value(app_id) - api_params['appSecret'] = self._normalize_value(app_secret) - api_params['enabled'] = self._normalize_value(enabled) + if app_id is not None: + api_params['appId'] = self._normalize_value(app_id) + if app_secret is not None: + api_params['appSecret'] = self._normalize_value(app_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1601,9 +1659,12 @@ def update_o_auth2_figma( api_path = '/project/oauth2/figma' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1649,10 +1710,14 @@ def update_o_auth2_fusion_auth( api_path = '/project/oauth2/fusionauth' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['endpoint'] = self._normalize_value(endpoint) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if endpoint is not None: + api_params['endpoint'] = self._normalize_value(endpoint) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1695,9 +1760,12 @@ def update_o_auth2_git_hub( api_path = '/project/oauth2/github' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1743,10 +1811,14 @@ def update_o_auth2_gitlab( api_path = '/project/oauth2/gitlab' api_params = {} - api_params['applicationId'] = self._normalize_value(application_id) - api_params['secret'] = self._normalize_value(secret) - api_params['endpoint'] = self._normalize_value(endpoint) - api_params['enabled'] = self._normalize_value(enabled) + if application_id is not None: + api_params['applicationId'] = self._normalize_value(application_id) + if secret is not None: + api_params['secret'] = self._normalize_value(secret) + if endpoint is not None: + api_params['endpoint'] = self._normalize_value(endpoint) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1792,10 +1864,14 @@ def update_o_auth2_google( api_path = '/project/oauth2/google' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['prompt'] = self._normalize_value(prompt) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if prompt is not None: + api_params['prompt'] = self._normalize_value(prompt) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1844,11 +1920,16 @@ def update_o_auth2_keycloak( api_path = '/project/oauth2/keycloak' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['endpoint'] = self._normalize_value(endpoint) - api_params['realmName'] = self._normalize_value(realm_name) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if endpoint is not None: + api_params['endpoint'] = self._normalize_value(endpoint) + if realm_name is not None: + api_params['realmName'] = self._normalize_value(realm_name) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1891,9 +1972,12 @@ def update_o_auth2_kick( api_path = '/project/oauth2/kick' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1936,9 +2020,12 @@ def update_o_auth2_linkedin( api_path = '/project/oauth2/linkedin' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['primaryClientSecret'] = self._normalize_value(primary_client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if primary_client_secret is not None: + api_params['primaryClientSecret'] = self._normalize_value(primary_client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1984,10 +2071,14 @@ def update_o_auth2_microsoft( api_path = '/project/oauth2/microsoft' api_params = {} - api_params['applicationId'] = self._normalize_value(application_id) - api_params['applicationSecret'] = self._normalize_value(application_secret) - api_params['tenant'] = self._normalize_value(tenant) - api_params['enabled'] = self._normalize_value(enabled) + if application_id is not None: + api_params['applicationId'] = self._normalize_value(application_id) + if application_secret is not None: + api_params['applicationSecret'] = self._normalize_value(application_secret) + if tenant is not None: + api_params['tenant'] = self._normalize_value(tenant) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2030,9 +2121,12 @@ def update_o_auth2_notion( api_path = '/project/oauth2/notion' api_params = {} - api_params['oauthClientId'] = self._normalize_value(oauth_client_id) - api_params['oauthClientSecret'] = self._normalize_value(oauth_client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if oauth_client_id is not None: + api_params['oauthClientId'] = self._normalize_value(oauth_client_id) + if oauth_client_secret is not None: + api_params['oauthClientSecret'] = self._normalize_value(oauth_client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2093,15 +2187,24 @@ def update_o_auth2_oidc( api_path = '/project/oauth2/oidc' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['wellKnownURL'] = self._normalize_value(well_known_url) - api_params['authorizationURL'] = self._normalize_value(authorization_url) - api_params['tokenURL'] = self._normalize_value(token_url) - api_params['userInfoURL'] = self._normalize_value(user_info_url) - api_params['prompt'] = self._normalize_value(prompt) - api_params['maxAge'] = self._normalize_value(max_age) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if well_known_url is not None: + api_params['wellKnownURL'] = self._normalize_value(well_known_url) + if authorization_url is not None: + api_params['authorizationURL'] = self._normalize_value(authorization_url) + if token_url is not None: + api_params['tokenURL'] = self._normalize_value(token_url) + if user_info_url is not None: + api_params['userInfoURL'] = self._normalize_value(user_info_url) + if prompt is not None: + api_params['prompt'] = self._normalize_value(prompt) + if max_age is not None: + api_params['maxAge'] = self._normalize_value(max_age) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2150,11 +2253,16 @@ def update_o_auth2_okta( api_path = '/project/oauth2/okta' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['domain'] = self._normalize_value(domain) - api_params['authorizationServerId'] = self._normalize_value(authorization_server_id) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if domain is not None: + api_params['domain'] = self._normalize_value(domain) + if authorization_server_id is not None: + api_params['authorizationServerId'] = self._normalize_value(authorization_server_id) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2197,9 +2305,12 @@ def update_o_auth2_paypal( api_path = '/project/oauth2/paypal' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['secretKey'] = self._normalize_value(secret_key) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if secret_key is not None: + api_params['secretKey'] = self._normalize_value(secret_key) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2242,9 +2353,12 @@ def update_o_auth2_paypal_sandbox( api_path = '/project/oauth2/paypalSandbox' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['secretKey'] = self._normalize_value(secret_key) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if secret_key is not None: + api_params['secretKey'] = self._normalize_value(secret_key) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2287,9 +2401,12 @@ def update_o_auth2_podio( api_path = '/project/oauth2/podio' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2332,9 +2449,12 @@ def update_o_auth2_salesforce( api_path = '/project/oauth2/salesforce' api_params = {} - api_params['customerKey'] = self._normalize_value(customer_key) - api_params['customerSecret'] = self._normalize_value(customer_secret) - api_params['enabled'] = self._normalize_value(enabled) + if customer_key is not None: + api_params['customerKey'] = self._normalize_value(customer_key) + if customer_secret is not None: + api_params['customerSecret'] = self._normalize_value(customer_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2377,9 +2497,12 @@ def update_o_auth2_slack( api_path = '/project/oauth2/slack' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2422,9 +2545,12 @@ def update_o_auth2_spotify( api_path = '/project/oauth2/spotify' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2467,9 +2593,12 @@ def update_o_auth2_stripe( api_path = '/project/oauth2/stripe' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['apiSecretKey'] = self._normalize_value(api_secret_key) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if api_secret_key is not None: + api_params['apiSecretKey'] = self._normalize_value(api_secret_key) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2512,9 +2641,12 @@ def update_o_auth2_tradeshift( api_path = '/project/oauth2/tradeshift' api_params = {} - api_params['oauth2ClientId'] = self._normalize_value(oauth2_client_id) - api_params['oauth2ClientSecret'] = self._normalize_value(oauth2_client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if oauth2_client_id is not None: + api_params['oauth2ClientId'] = self._normalize_value(oauth2_client_id) + if oauth2_client_secret is not None: + api_params['oauth2ClientSecret'] = self._normalize_value(oauth2_client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2557,9 +2689,12 @@ def update_o_auth2_tradeshift_sandbox( api_path = '/project/oauth2/tradeshiftBox' api_params = {} - api_params['oauth2ClientId'] = self._normalize_value(oauth2_client_id) - api_params['oauth2ClientSecret'] = self._normalize_value(oauth2_client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if oauth2_client_id is not None: + api_params['oauth2ClientId'] = self._normalize_value(oauth2_client_id) + if oauth2_client_secret is not None: + api_params['oauth2ClientSecret'] = self._normalize_value(oauth2_client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2602,9 +2737,12 @@ def update_o_auth2_twitch( api_path = '/project/oauth2/twitch' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2647,9 +2785,12 @@ def update_o_auth2_word_press( api_path = '/project/oauth2/wordpress' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2692,9 +2833,12 @@ def update_o_auth2_x( api_path = '/project/oauth2/x' api_params = {} - api_params['customerKey'] = self._normalize_value(customer_key) - api_params['secretKey'] = self._normalize_value(secret_key) - api_params['enabled'] = self._normalize_value(enabled) + if customer_key is not None: + api_params['customerKey'] = self._normalize_value(customer_key) + if secret_key is not None: + api_params['secretKey'] = self._normalize_value(secret_key) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2737,9 +2881,12 @@ def update_o_auth2_yahoo( api_path = '/project/oauth2/yahoo' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2782,9 +2929,12 @@ def update_o_auth2_yandex( api_path = '/project/oauth2/yandex' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2827,9 +2977,12 @@ def update_o_auth2_zoho( api_path = '/project/oauth2/zoho' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2872,9 +3025,12 @@ def update_o_auth2_zoom( api_path = '/project/oauth2/zoom' api_params = {} - api_params['clientId'] = self._normalize_value(client_id) - api_params['clientSecret'] = self._normalize_value(client_secret) - api_params['enabled'] = self._normalize_value(enabled) + if client_id is not None: + api_params['clientId'] = self._normalize_value(client_id) + if client_secret is not None: + api_params['clientSecret'] = self._normalize_value(client_secret) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4589,16 +4745,26 @@ def update_smtp( api_path = '/project/smtp' api_params = {} - api_params['host'] = self._normalize_value(host) - api_params['port'] = self._normalize_value(port) - api_params['username'] = self._normalize_value(username) - api_params['password'] = self._normalize_value(password) - api_params['senderEmail'] = self._normalize_value(sender_email) - api_params['senderName'] = self._normalize_value(sender_name) - api_params['replyToEmail'] = self._normalize_value(reply_to_email) - api_params['replyToName'] = self._normalize_value(reply_to_name) - api_params['secure'] = self._normalize_value(secure) - api_params['enabled'] = self._normalize_value(enabled) + if host is not None: + api_params['host'] = self._normalize_value(host) + if port is not None: + api_params['port'] = self._normalize_value(port) + if username is not None: + api_params['username'] = self._normalize_value(username) + if password is not None: + api_params['password'] = self._normalize_value(password) + if sender_email is not None: + api_params['senderEmail'] = self._normalize_value(sender_email) + if sender_name is not None: + api_params['senderName'] = self._normalize_value(sender_name) + if reply_to_email is not None: + api_params['replyToEmail'] = self._normalize_value(reply_to_email) + if reply_to_name is not None: + api_params['replyToName'] = self._normalize_value(reply_to_name) + if secure is not None: + api_params['secure'] = self._normalize_value(secure) + if enabled is not None: + api_params['enabled'] = self._normalize_value(enabled) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4743,12 +4909,18 @@ def update_email_template( api_params['templateId'] = self._normalize_value(template_id) if locale is not None: api_params['locale'] = self._normalize_value(locale) - api_params['subject'] = self._normalize_value(subject) - api_params['message'] = self._normalize_value(message) - api_params['senderName'] = self._normalize_value(sender_name) - api_params['senderEmail'] = self._normalize_value(sender_email) - api_params['replyToEmail'] = self._normalize_value(reply_to_email) - api_params['replyToName'] = self._normalize_value(reply_to_name) + if subject is not None: + api_params['subject'] = self._normalize_value(subject) + if message is not None: + api_params['message'] = self._normalize_value(message) + if sender_name is not None: + api_params['senderName'] = self._normalize_value(sender_name) + if sender_email is not None: + api_params['senderEmail'] = self._normalize_value(sender_email) + if reply_to_email is not None: + api_params['replyToEmail'] = self._normalize_value(reply_to_email) + if reply_to_name is not None: + api_params['replyToName'] = self._normalize_value(reply_to_name) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4982,9 +5154,12 @@ def update_variable( api_path = api_path.replace('{variableId}', str(self._normalize_value(variable_id))) - api_params['key'] = self._normalize_value(key) - api_params['value'] = self._normalize_value(value) - api_params['secret'] = self._normalize_value(secret) + if key is not None: + api_params['key'] = self._normalize_value(key) + if value is not None: + api_params['value'] = self._normalize_value(value) + if secret is not None: + api_params['secret'] = self._normalize_value(secret) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/sites.py b/appwrite/services/sites.py index 4daf161f..611dcb02 100644 --- a/appwrite/services/sites.py +++ b/appwrite/services/sites.py @@ -467,9 +467,12 @@ def update( api_params['providerSilentMode'] = self._normalize_value(provider_silent_mode) if provider_root_directory is not None: api_params['providerRootDirectory'] = self._normalize_value(provider_root_directory) - api_params['providerBranches'] = self._normalize_value(provider_branches) - api_params['providerPaths'] = self._normalize_value(provider_paths) - api_params['buildSpecification'] = self._normalize_value(build_specification) + if provider_branches is not None: + api_params['providerBranches'] = self._normalize_value(provider_branches) + if provider_paths is not None: + api_params['providerPaths'] = self._normalize_value(provider_paths) + if build_specification is not None: + api_params['buildSpecification'] = self._normalize_value(build_specification) if runtime_specification is not None: api_params['runtimeSpecification'] = self._normalize_value(runtime_specification) if deployment_retention is not None: @@ -1428,9 +1431,12 @@ def update_variable( api_path = api_path.replace('{siteId}', str(self._normalize_value(site_id))) api_path = api_path.replace('{variableId}', str(self._normalize_value(variable_id))) - api_params['key'] = self._normalize_value(key) - api_params['value'] = self._normalize_value(value) - api_params['secret'] = self._normalize_value(secret) + if key is not None: + api_params['key'] = self._normalize_value(key) + if value is not None: + api_params['value'] = self._normalize_value(value) + if secret is not None: + api_params['secret'] = self._normalize_value(secret) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/storage.py b/appwrite/services/storage.py index 7d1a1eaf..a450e58b 100644 --- a/appwrite/services/storage.py +++ b/appwrite/services/storage.py @@ -128,7 +128,8 @@ def create_bucket( api_params['bucketId'] = self._normalize_value(bucket_id) api_params['name'] = self._normalize_value(name) - api_params['permissions'] = self._normalize_value(permissions) + if permissions is not None: + api_params['permissions'] = self._normalize_value(permissions) if file_security is not None: api_params['fileSecurity'] = self._normalize_value(file_security) if enabled is not None: @@ -258,7 +259,8 @@ def update_bucket( api_path = api_path.replace('{bucketId}', str(self._normalize_value(bucket_id))) api_params['name'] = self._normalize_value(name) - api_params['permissions'] = self._normalize_value(permissions) + if permissions is not None: + api_params['permissions'] = self._normalize_value(permissions) if file_security is not None: api_params['fileSecurity'] = self._normalize_value(file_security) if enabled is not None: @@ -544,7 +546,8 @@ def update_file( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['permissions'] = self._normalize_value(permissions) + if permissions is not None: + api_params['permissions'] = self._normalize_value(permissions) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/tables_db.py b/appwrite/services/tables_db.py index 9733bc96..806bdc84 100644 --- a/appwrite/services/tables_db.py +++ b/appwrite/services/tables_db.py @@ -638,7 +638,8 @@ def create_table( api_params['tableId'] = self._normalize_value(table_id) api_params['name'] = self._normalize_value(name) - api_params['permissions'] = self._normalize_value(permissions) + if permissions is not None: + api_params['permissions'] = self._normalize_value(permissions) if row_security is not None: api_params['rowSecurity'] = self._normalize_value(row_security) if enabled is not None: @@ -757,7 +758,8 @@ def update_table( if name is not None: api_params['name'] = self._normalize_value(name) - api_params['permissions'] = self._normalize_value(permissions) + if permissions is not None: + api_params['permissions'] = self._normalize_value(permissions) if row_security is not None: api_params['rowSecurity'] = self._normalize_value(row_security) if enabled is not None: @@ -940,9 +942,12 @@ def create_big_int_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['min'] = self._normalize_value(min) - api_params['max'] = self._normalize_value(max) - api_params['default'] = self._normalize_value(default) + if min is not None: + api_params['min'] = self._normalize_value(min) + if max is not None: + api_params['max'] = self._normalize_value(max) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1019,10 +1024,13 @@ def update_big_int_column( api_path = api_path.replace('{key}', str(self._normalize_value(key))) api_params['required'] = self._normalize_value(required) - api_params['min'] = self._normalize_value(min) - api_params['max'] = self._normalize_value(max) + if min is not None: + api_params['min'] = self._normalize_value(min) + if max is not None: + api_params['max'] = self._normalize_value(max) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1091,7 +1099,8 @@ def create_boolean_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1162,7 +1171,8 @@ def update_boolean_column( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1230,7 +1240,8 @@ def create_datetime_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1301,7 +1312,8 @@ def update_datetime_column( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1370,7 +1382,8 @@ def create_email_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1442,7 +1455,8 @@ def update_email_column( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1517,7 +1531,8 @@ def create_enum_column( api_params['key'] = self._normalize_value(key) api_params['elements'] = self._normalize_value(elements) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1596,7 +1611,8 @@ def update_enum_column( api_params['elements'] = self._normalize_value(elements) api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1671,9 +1687,12 @@ def create_float_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['min'] = self._normalize_value(min) - api_params['max'] = self._normalize_value(max) - api_params['default'] = self._normalize_value(default) + if min is not None: + api_params['min'] = self._normalize_value(min) + if max is not None: + api_params['max'] = self._normalize_value(max) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1750,10 +1769,13 @@ def update_float_column( api_path = api_path.replace('{key}', str(self._normalize_value(key))) api_params['required'] = self._normalize_value(required) - api_params['min'] = self._normalize_value(min) - api_params['max'] = self._normalize_value(max) + if min is not None: + api_params['min'] = self._normalize_value(min) + if max is not None: + api_params['max'] = self._normalize_value(max) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1828,9 +1850,12 @@ def create_integer_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['min'] = self._normalize_value(min) - api_params['max'] = self._normalize_value(max) - api_params['default'] = self._normalize_value(default) + if min is not None: + api_params['min'] = self._normalize_value(min) + if max is not None: + api_params['max'] = self._normalize_value(max) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -1907,10 +1932,13 @@ def update_integer_column( api_path = api_path.replace('{key}', str(self._normalize_value(key))) api_params['required'] = self._normalize_value(required) - api_params['min'] = self._normalize_value(min) - api_params['max'] = self._normalize_value(max) + if min is not None: + api_params['min'] = self._normalize_value(min) + if max is not None: + api_params['max'] = self._normalize_value(max) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -1979,7 +2007,8 @@ def create_ip_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -2051,7 +2080,8 @@ def update_ip_column( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2116,7 +2146,8 @@ def create_line_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2184,8 +2215,10 @@ def update_line_column( api_path = api_path.replace('{key}', str(self._normalize_value(key))) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if default is not None: + api_params['default'] = self._normalize_value(default) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2257,7 +2290,8 @@ def create_longtext_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) if encrypt is not None: @@ -2331,7 +2365,8 @@ def update_longtext_column( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2403,7 +2438,8 @@ def create_mediumtext_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) if encrypt is not None: @@ -2477,7 +2513,8 @@ def update_mediumtext_column( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2542,7 +2579,8 @@ def create_point_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2610,8 +2648,10 @@ def update_point_column( api_path = api_path.replace('{key}', str(self._normalize_value(key))) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if default is not None: + api_params['default'] = self._normalize_value(default) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2676,7 +2716,8 @@ def create_polygon_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2744,8 +2785,10 @@ def update_polygon_column( api_path = api_path.replace('{key}', str(self._normalize_value(key))) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if default is not None: + api_params['default'] = self._normalize_value(default) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -2822,8 +2865,10 @@ def create_relationship_column( api_params['type'] = self._normalize_value(type) if two_way is not None: api_params['twoWay'] = self._normalize_value(two_way) - api_params['key'] = self._normalize_value(key) - api_params['twoWayKey'] = self._normalize_value(two_way_key) + if key is not None: + api_params['key'] = self._normalize_value(key) + if two_way_key is not None: + api_params['twoWayKey'] = self._normalize_value(two_way_key) if on_delete is not None: api_params['onDelete'] = self._normalize_value(on_delete) @@ -2907,7 +2952,8 @@ def create_string_column( api_params['key'] = self._normalize_value(key) api_params['size'] = self._normalize_value(size) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) if encrypt is not None: @@ -2987,8 +3033,10 @@ def update_string_column( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['size'] = self._normalize_value(size) - api_params['newKey'] = self._normalize_value(new_key) + if size is not None: + api_params['size'] = self._normalize_value(size) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -3060,7 +3108,8 @@ def create_text_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) if encrypt is not None: @@ -3134,7 +3183,8 @@ def update_text_column( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -3203,7 +3253,8 @@ def create_url_column( api_params['key'] = self._normalize_value(key) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) @@ -3275,7 +3326,8 @@ def update_url_column( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['newKey'] = self._normalize_value(new_key) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -3354,7 +3406,8 @@ def create_varchar_column( api_params['key'] = self._normalize_value(key) api_params['size'] = self._normalize_value(size) api_params['required'] = self._normalize_value(required) - api_params['default'] = self._normalize_value(default) + if default is not None: + api_params['default'] = self._normalize_value(default) if array is not None: api_params['array'] = self._normalize_value(array) if encrypt is not None: @@ -3431,8 +3484,10 @@ def update_varchar_column( api_params['required'] = self._normalize_value(required) api_params['default'] = self._normalize_value(default) - api_params['size'] = self._normalize_value(size) - api_params['newKey'] = self._normalize_value(new_key) + if size is not None: + api_params['size'] = self._normalize_value(size) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -3632,8 +3687,10 @@ def update_relationship_column( api_path = api_path.replace('{tableId}', str(self._normalize_value(table_id))) api_path = api_path.replace('{key}', str(self._normalize_value(key))) - api_params['onDelete'] = self._normalize_value(on_delete) - api_params['newKey'] = self._normalize_value(new_key) + if on_delete is not None: + api_params['onDelete'] = self._normalize_value(on_delete) + if new_key is not None: + api_params['newKey'] = self._normalize_value(new_key) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4016,8 +4073,10 @@ def create_row( api_params['rowId'] = self._normalize_value(row_id) api_params['data'] = self._normalize_value(data) - api_params['permissions'] = self._normalize_value(permissions) - api_params['transactionId'] = self._normalize_value(transaction_id) + 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', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4079,7 +4138,8 @@ def create_rows( api_path = api_path.replace('{tableId}', str(self._normalize_value(table_id))) api_params['rows'] = self._normalize_value(rows) - api_params['transactionId'] = self._normalize_value(transaction_id) + if transaction_id is not None: + api_params['transactionId'] = self._normalize_value(transaction_id) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4142,7 +4202,8 @@ def upsert_rows( api_path = api_path.replace('{tableId}', str(self._normalize_value(table_id))) api_params['rows'] = self._normalize_value(rows) - api_params['transactionId'] = self._normalize_value(transaction_id) + if transaction_id is not None: + api_params['transactionId'] = self._normalize_value(transaction_id) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4207,7 +4268,8 @@ def update_rows( api_params['data'] = self._normalize_value(data) if queries is not None: api_params['queries'] = self._normalize_value(queries) - api_params['transactionId'] = self._normalize_value(transaction_id) + if transaction_id is not None: + api_params['transactionId'] = self._normalize_value(transaction_id) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4405,8 +4467,10 @@ def upsert_row( if data is not None: api_params['data'] = self._normalize_value(data) - api_params['permissions'] = self._normalize_value(permissions) - api_params['transactionId'] = self._normalize_value(transaction_id) + 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('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4476,8 +4540,10 @@ def update_row( if data is not None: api_params['data'] = self._normalize_value(data) - api_params['permissions'] = self._normalize_value(permissions) - api_params['transactionId'] = self._normalize_value(transaction_id) + 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('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4612,8 +4678,10 @@ def decrement_row_column( if value is not None: api_params['value'] = self._normalize_value(value) - api_params['min'] = self._normalize_value(min) - api_params['transactionId'] = self._normalize_value(transaction_id) + if min is not None: + api_params['min'] = self._normalize_value(min) + if transaction_id is not None: + api_params['transactionId'] = self._normalize_value(transaction_id) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4690,8 +4758,10 @@ def increment_row_column( if value is not None: api_params['value'] = self._normalize_value(value) - api_params['max'] = self._normalize_value(max) - api_params['transactionId'] = self._normalize_value(transaction_id) + if max is not None: + api_params['max'] = self._normalize_value(max) + if transaction_id is not None: + api_params['transactionId'] = self._normalize_value(transaction_id) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/tokens.py b/appwrite/services/tokens.py index 0c04dfe3..bdff3a61 100644 --- a/appwrite/services/tokens.py +++ b/appwrite/services/tokens.py @@ -107,7 +107,8 @@ def create_file_token( api_path = api_path.replace('{bucketId}', str(self._normalize_value(bucket_id))) api_path = api_path.replace('{fileId}', str(self._normalize_value(file_id))) - api_params['expire'] = self._normalize_value(expire) + if expire is not None: + api_params['expire'] = self._normalize_value(expire) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -190,7 +191,8 @@ def update( api_path = api_path.replace('{tokenId}', str(self._normalize_value(token_id))) - api_params['expire'] = self._normalize_value(expire) + if expire is not None: + api_params['expire'] = self._normalize_value(expire) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/users.py b/appwrite/services/users.py index c0e282e6..8eab2dc7 100644 --- a/appwrite/services/users.py +++ b/appwrite/services/users.py @@ -125,8 +125,10 @@ def create( api_params['userId'] = self._normalize_value(user_id) - api_params['email'] = self._normalize_value(email) - api_params['phone'] = self._normalize_value(phone) + if email is not None: + api_params['email'] = self._normalize_value(email) + if phone is not None: + api_params['phone'] = self._normalize_value(phone) if password is not None: api_params['password'] = self._normalize_value(password) if name is not None: diff --git a/appwrite/services/webhooks.py b/appwrite/services/webhooks.py index b9c99e8e..9e998f36 100644 --- a/appwrite/services/webhooks.py +++ b/appwrite/services/webhooks.py @@ -127,7 +127,8 @@ def create( 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) + if secret is not None: + api_params['secret'] = self._normalize_value(secret) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -330,7 +331,8 @@ def update_secret( api_path = api_path.replace('{webhookId}', str(self._normalize_value(webhook_id))) - api_params['secret'] = self._normalize_value(secret) + if secret is not None: + api_params['secret'] = self._normalize_value(secret) response = self.client.call('patch', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), From 122cd9b2dd4a79046d52f7f1eb11533f0ff2e9a9 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 22 Jul 2026 11:02:50 +0530 Subject: [PATCH 3/8] chore: update Python SDK to 22.1.0 --- CHANGELOG.md | 12 + appwrite/client.py | 4 +- appwrite/enums/database_status.py | 11 + appwrite/enums/database_type.py | 3 + appwrite/enums/project_key_scopes.py | 3 + appwrite/models/__init__.py | 32 +++ appwrite/models/activity_event.py | 33 +++ appwrite/models/app_scope.py | 27 ++ appwrite/models/app_scope_list.py | 19 ++ appwrite/models/app_secret.py | 2 +- appwrite/models/app_secret_plaintext.py | 2 +- appwrite/models/billing_plan.py | 3 + appwrite/models/database.py | 13 +- appwrite/models/database_status.py | 39 +++ .../models/database_status_connections.py | 18 ++ appwrite/models/database_status_replica.py | 24 ++ appwrite/models/database_status_volume.py | 24 ++ appwrite/models/dedicated_database.py | 159 +++++++++++ appwrite/models/dedicated_database_member.py | 24 ++ .../models/dedicated_database_replicas.py | 22 ++ .../dedicated_database_specification.py | 39 +++ .../dedicated_database_specification_list.py | 23 ++ ...edicated_database_specification_pricing.py | 27 ++ appwrite/models/oauth2_consent.py | 42 +++ appwrite/models/oauth2_consent_list.py | 19 ++ appwrite/models/oauth2_consent_token.py | 45 +++ appwrite/models/oauth2_consent_token_list.py | 19 ++ appwrite/models/project.py | 3 + appwrite/services/account.py | 267 ++++++++++++++++++ appwrite/services/apps.py | 39 ++- appwrite/services/backups.py | 17 +- appwrite/services/oauth2.py | 113 +++++++- appwrite/services/sites.py | 7 +- appwrite/services/tables_db.py | 169 ++++++++++- docs/examples/account/delete-consent-token.md | 16 ++ docs/examples/account/delete-consent.md | 15 + docs/examples/account/get-consent-token.md | 19 ++ docs/examples/account/get-consent.md | 18 ++ docs/examples/account/list-consent-tokens.md | 20 ++ docs/examples/account/list-consents.md | 19 ++ docs/examples/apps/list-o-auth-2-scopes.md | 16 ++ docs/examples/backups/create-restoration.md | 3 +- docs/examples/oauth2/authorize-post.md | 31 ++ .../examples/sites/get-deployment-download.md | 3 +- docs/examples/tablesdb/create-failover.md | 19 ++ docs/examples/tablesdb/create.md | 3 +- docs/examples/tablesdb/get-replicas.md | 18 ++ docs/examples/tablesdb/get-status.md | 18 ++ docs/examples/tablesdb/list-specifications.md | 16 ++ docs/examples/tablesdb/update.md | 3 +- pyproject.toml | 2 +- setup.py | 4 +- test/services/test_account.py | 102 +++++++ test/services/test_activities.py | 13 +- test/services/test_apps.py | 16 +- test/services/test_backups.py | 4 +- test/services/test_databases.py | 12 +- test/services/test_oauth2.py | 14 + test/services/test_organization.py | 11 +- test/services/test_project.py | 60 ++-- test/services/test_tables_db.py | 133 ++++++++- 61 files changed, 1828 insertions(+), 83 deletions(-) create mode 100644 appwrite/models/app_scope.py create mode 100644 appwrite/models/app_scope_list.py create mode 100644 appwrite/models/database_status.py create mode 100644 appwrite/models/database_status_connections.py create mode 100644 appwrite/models/database_status_replica.py create mode 100644 appwrite/models/database_status_volume.py create mode 100644 appwrite/models/dedicated_database.py create mode 100644 appwrite/models/dedicated_database_member.py create mode 100644 appwrite/models/dedicated_database_replicas.py create mode 100644 appwrite/models/dedicated_database_specification.py create mode 100644 appwrite/models/dedicated_database_specification_list.py create mode 100644 appwrite/models/dedicated_database_specification_pricing.py create mode 100644 appwrite/models/oauth2_consent.py create mode 100644 appwrite/models/oauth2_consent_list.py create mode 100644 appwrite/models/oauth2_consent_token.py create mode 100644 appwrite/models/oauth2_consent_token_list.py create mode 100644 docs/examples/account/delete-consent-token.md create mode 100644 docs/examples/account/delete-consent.md create mode 100644 docs/examples/account/get-consent-token.md create mode 100644 docs/examples/account/get-consent.md create mode 100644 docs/examples/account/list-consent-tokens.md create mode 100644 docs/examples/account/list-consents.md create mode 100644 docs/examples/apps/list-o-auth-2-scopes.md create mode 100644 docs/examples/oauth2/authorize-post.md create mode 100644 docs/examples/tablesdb/create-failover.md create mode 100644 docs/examples/tablesdb/get-replicas.md create mode 100644 docs/examples/tablesdb/get-status.md create mode 100644 docs/examples/tablesdb/list-specifications.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e66e10b9..d0f3fdfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 22.1.0 + +* Updated: removed `new_specification` parameter from `backups.create_restoration` +* Updated: `Database.policies` and `Database.archives` are now optional +* Added: account OAuth2 consent methods `list_consents`, `get_consent`, `delete_consent` and consent token methods +* Added: `Oauth2Consent` and `Oauth2ConsentToken` models +* Added: `oauth2.authorize_post` method and `oauth2.introspect` key scope +* Added: `apps.list_o_auth2_scopes` method and `AppScope` model +* Added: geolocation, connection, and SDK attribution fields on `ActivityEvent` +* Added: `token` parameter to `sites.get_deployment_download` +* Updated: `AppSecret.secret` is always empty; the secret is returned only on creation + ## 22.0.0 * Breaking: removed `Health` service and all health models and enums diff --git a/appwrite/client.py b/appwrite/client.py index 0fc438b7..3588e3a7 100644 --- a/appwrite/client.py +++ b/appwrite/client.py @@ -17,11 +17,11 @@ def __init__(self): self._endpoint = 'https://cloud.appwrite.io/v1' self._global_headers = { 'content-type': '', - 'user-agent' : f'AppwritePythonSDK/22.0.0 ({platform.uname().system}; {platform.uname().version}; {platform.uname().machine})', + 'user-agent' : f'AppwritePythonSDK/22.1.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': '22.0.0', + 'x-sdk-version': '22.1.0', 'X-Appwrite-Response-Format' : '1.9.5', } self._config = {} diff --git a/appwrite/enums/database_status.py b/appwrite/enums/database_status.py index 7c5f888f..ed43ed1a 100644 --- a/appwrite/enums/database_status.py +++ b/appwrite/enums/database_status.py @@ -3,4 +3,15 @@ class DatabaseStatus(Enum): PROVISIONING = "provisioning" READY = "ready" + INACTIVE = "inactive" + PAUSED = "paused" FAILED = "failed" + DELETING = "deleting" + DELETED = "deleted" + RESTORING = "restoring" + SCALING = "scaling" + UPGRADING = "upgrading" + MIGRATING = "migrating" + PAUSING = "pausing" + RESUMING = "resuming" + FAILING_OVER = "failing-over" diff --git a/appwrite/enums/database_type.py b/appwrite/enums/database_type.py index 5d2d0aeb..0b713567 100644 --- a/appwrite/enums/database_type.py +++ b/appwrite/enums/database_type.py @@ -5,3 +5,6 @@ class DatabaseType(Enum): TABLESDB = "tablesdb" DOCUMENTSDB = "documentsdb" VECTORSDB = "vectorsdb" + MYSQL = "mysql" + POSTGRESQL = "postgresql" + MONGODB = "mongodb" diff --git a/appwrite/enums/project_key_scopes.py b/appwrite/enums/project_key_scopes.py index 10099119..e29a2454 100644 --- a/appwrite/enums/project_key_scopes.py +++ b/appwrite/enums/project_key_scopes.py @@ -96,9 +96,12 @@ class ProjectKeyScopes(Enum): DEDICATEDDATABASES_EXECUTE = "dedicatedDatabases.execute" DOMAINS_READ = "domains.read" DOMAINS_WRITE = "domains.write" + WAFRULES_READ = "wafRules.read" + WAFRULES_WRITE = "wafRules.write" EVENTS_READ = "events.read" APPS_READ = "apps.read" APPS_WRITE = "apps.write" OAUTH2_READ = "oauth2.read" OAUTH2_WRITE = "oauth2.write" + OAUTH2_INTROSPECT = "oauth2.introspect" USAGE_READ = "usage.read" diff --git a/appwrite/models/__init__.py b/appwrite/models/__init__.py index 8e75504a..07a73030 100644 --- a/appwrite/models/__init__.py +++ b/appwrite/models/__init__.py @@ -220,6 +220,10 @@ from .billing_plan_dedicated_database_limits import BillingPlanDedicatedDatabaseLimits from .billing_plan_supported_addons import BillingPlanSupportedAddons from .block import Block +from .dedicated_database import DedicatedDatabase +from .database_status import DatabaseStatus +from .dedicated_database_member import DedicatedDatabaseMember +from .dedicated_database_replicas import DedicatedDatabaseReplicas from .organization import Organization from .backup_policy import BackupPolicy from .policy_deny_aliased_email import PolicyDenyAliasedEmail @@ -228,10 +232,17 @@ from .policy_deny_corporate_email import PolicyDenyCorporateEmail from .program import Program from .backup_restoration import BackupRestoration +from .dedicated_database_specification import DedicatedDatabaseSpecification +from .dedicated_database_specification_list import DedicatedDatabaseSpecificationList +from .dedicated_database_specification_pricing import DedicatedDatabaseSpecificationPricing +from .database_status_connections import DatabaseStatusConnections +from .database_status_replica import DatabaseStatusReplica +from .database_status_volume import DatabaseStatusVolume from .usage_billing_plan import UsageBillingPlan from .app import App from .app_secret import AppSecret from .app_secret_plaintext import AppSecretPlaintext +from .app_scope import AppScope from .oauth2_authorize import Oauth2Authorize from .oauth2_approve import Oauth2Approve from .oauth2_reject import Oauth2Reject @@ -239,16 +250,21 @@ from .oauth2_device_authorization import Oauth2DeviceAuthorization from .oauth2_par import Oauth2PAR from .oauth2_token import Oauth2Token +from .oauth2_consent import Oauth2Consent +from .oauth2_consent_token import Oauth2ConsentToken from .oauth2_project import Oauth2Project from .oauth2_organization import Oauth2Organization from .oauth2_project_list import Oauth2ProjectList from .oauth2_organization_list import Oauth2OrganizationList +from .oauth2_consent_list import Oauth2ConsentList +from .oauth2_consent_token_list import Oauth2ConsentTokenList from .activity_event_list import ActivityEventList from .backup_archive_list import BackupArchiveList from .backup_policy_list import BackupPolicyList from .backup_restoration_list import BackupRestorationList from .apps_list import AppsList from .app_secret_list import AppSecretList +from .app_scope_list import AppScopeList __all__ = [ 'AppwriteModel', @@ -473,6 +489,10 @@ 'BillingPlanDedicatedDatabaseLimits', 'BillingPlanSupportedAddons', 'Block', + 'DedicatedDatabase', + 'DatabaseStatus', + 'DedicatedDatabaseMember', + 'DedicatedDatabaseReplicas', 'Organization', 'BackupPolicy', 'PolicyDenyAliasedEmail', @@ -481,10 +501,17 @@ 'PolicyDenyCorporateEmail', 'Program', 'BackupRestoration', + 'DedicatedDatabaseSpecification', + 'DedicatedDatabaseSpecificationList', + 'DedicatedDatabaseSpecificationPricing', + 'DatabaseStatusConnections', + 'DatabaseStatusReplica', + 'DatabaseStatusVolume', 'UsageBillingPlan', 'App', 'AppSecret', 'AppSecretPlaintext', + 'AppScope', 'Oauth2Authorize', 'Oauth2Approve', 'Oauth2Reject', @@ -492,14 +519,19 @@ 'Oauth2DeviceAuthorization', 'Oauth2PAR', 'Oauth2Token', + 'Oauth2Consent', + 'Oauth2ConsentToken', 'Oauth2Project', 'Oauth2Organization', 'Oauth2ProjectList', 'Oauth2OrganizationList', + 'Oauth2ConsentList', + 'Oauth2ConsentTokenList', 'ActivityEventList', 'BackupArchiveList', 'BackupPolicyList', 'BackupRestorationList', 'AppsList', 'AppSecretList', + 'AppScopeList', ] diff --git a/appwrite/models/activity_event.py b/appwrite/models/activity_event.py index 11265c1d..eddec37d 100644 --- a/appwrite/models/activity_event.py +++ b/appwrite/models/activity_event.py @@ -37,6 +37,24 @@ class ActivityEvent(AppwriteModel): API mode when event triggered. country : str Location. + continentcode : str + Continent code. + city : str + City name. + subdivisions : str + Region/state chain. + isp : str + Internet service provider. + autonomoussystemnumber : str + Autonomous System Number (ASN). + autonomoussystemorganization : str + Organization that owns the ASN. + connectiontype : str + Connection type (e.g. cable, cellular, corporate). + connectionusagetype : str + User type (e.g. residential, business, hosting). + connectionorganization : str + Registered organization of the IP. time : str Log creation date in ISO 8601 format. projectid : str @@ -45,6 +63,10 @@ class ActivityEvent(AppwriteModel): Team ID. hostname : str Hostname. + sdk : str + Name of the SDK that triggered the event. + sdkversion : str + Version of the SDK that triggered the event. """ id: str = Field(..., alias='$id') actortype: str = Field(..., alias='actorType') @@ -60,7 +82,18 @@ class ActivityEvent(AppwriteModel): ip: str = Field(..., alias='ip') mode: str = Field(..., alias='mode') country: str = Field(..., alias='country') + continentcode: str = Field(..., alias='continentCode') + city: str = Field(..., alias='city') + subdivisions: str = Field(..., alias='subdivisions') + isp: str = Field(..., alias='isp') + autonomoussystemnumber: str = Field(..., alias='autonomousSystemNumber') + autonomoussystemorganization: str = Field(..., alias='autonomousSystemOrganization') + connectiontype: str = Field(..., alias='connectionType') + connectionusagetype: str = Field(..., alias='connectionUsageType') + connectionorganization: str = Field(..., alias='connectionOrganization') time: str = Field(..., alias='time') projectid: str = Field(..., alias='projectId') teamid: str = Field(..., alias='teamId') hostname: str = Field(..., alias='hostname') + sdk: str = Field(..., alias='sdk') + sdkversion: str = Field(..., alias='sdkVersion') diff --git a/appwrite/models/app_scope.py b/appwrite/models/app_scope.py new file mode 100644 index 00000000..4dca2ae2 --- /dev/null +++ b/appwrite/models/app_scope.py @@ -0,0 +1,27 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class AppScope(AppwriteModel): + """ + AppScope + + Attributes + ---------- + value : str + Scope value as requested by apps. + description : str + Human-readable description of what the scope grants. + type : str + What the scope grants access to. One of `account`, `project`, or `organization`. Only `project` and `organization` scopes are installable. + category : str + Scope category, used to group scopes on consent and installation screens. + deprecated : bool + Whether the scope is deprecated. Deprecated scopes can still be requested but should not be offered for new grants. + """ + value: str = Field(..., alias='value') + description: str = Field(..., alias='description') + type: str = Field(..., alias='type') + category: str = Field(..., alias='category') + deprecated: bool = Field(..., alias='deprecated') diff --git a/appwrite/models/app_scope_list.py b/appwrite/models/app_scope_list.py new file mode 100644 index 00000000..f92908e7 --- /dev/null +++ b/appwrite/models/app_scope_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .app_scope import AppScope + +class AppScopeList(AppwriteModel): + """ + App scopes list + + Attributes + ---------- + total : float + Total number of scopes that matched your query. + scopes : List[AppScope] + List of scopes. + """ + total: float = Field(..., alias='total') + scopes: List[AppScope] = Field(..., alias='scopes') diff --git a/appwrite/models/app_secret.py b/appwrite/models/app_secret.py index dc7def83..348b2cbf 100644 --- a/appwrite/models/app_secret.py +++ b/appwrite/models/app_secret.py @@ -18,7 +18,7 @@ class AppSecret(AppwriteModel): appid : str Application ID this secret belongs to. secret : str - Hashed application client secret. + Always empty. The application client secret is returned only once, in the response of the createSecret method. hint : str Last few characters of the client secret, used to help identify it. createdbyid : str diff --git a/appwrite/models/app_secret_plaintext.py b/appwrite/models/app_secret_plaintext.py index ec6d9ae5..18a32d13 100644 --- a/appwrite/models/app_secret_plaintext.py +++ b/appwrite/models/app_secret_plaintext.py @@ -18,7 +18,7 @@ class AppSecretPlaintext(AppwriteModel): appid : str Application ID this secret belongs to. secret : str - Application client secret. Returned in full only when the secret is created; subsequent reads return a masked value. + Application client secret. Returned only when the secret is created; subsequent reads always return an empty value. hint : str Last few characters of the client secret, used to help identify it. createdbyid : str diff --git a/appwrite/models/billing_plan.py b/appwrite/models/billing_plan.py index 64db521b..c37db9d3 100644 --- a/appwrite/models/billing_plan.py +++ b/appwrite/models/billing_plan.py @@ -40,6 +40,8 @@ class BillingPlan(AppwriteModel): Members webhooks : float Webhooks + wafrules : float + Maximum WAF rules per project projects : float Projects platforms : float @@ -165,6 +167,7 @@ class BillingPlan(AppwriteModel): screenshotsgenerated: float = Field(..., alias='screenshotsGenerated') members: float = Field(..., alias='members') webhooks: float = Field(..., alias='webhooks') + wafrules: float = Field(..., alias='wafRules') projects: float = Field(..., alias='projects') platforms: float = Field(..., alias='platforms') users: float = Field(..., alias='users') diff --git a/appwrite/models/database.py b/appwrite/models/database.py index b491a89a..f83198fd 100644 --- a/appwrite/models/database.py +++ b/appwrite/models/database.py @@ -26,10 +26,12 @@ class Database(AppwriteModel): type : DatabaseType Database type. status : Optional[DatabaseStatus] - Database status. Possible values: `provisioning`, `ready` or `failed` - policies : List[BackupPolicy] + Dedicated database lifecycle status. Null when the database has no valid dedicated backing. + replicas : Optional[float] + Number of secondary high availability replicas, excluding the primary. Null when backing configuration is unavailable. + policies : Optional[List[BackupPolicy]] Database backup policies. - archives : List[BackupArchive] + archives : Optional[List[BackupArchive]] Database backup archives. """ id: str = Field(..., alias='$id') @@ -39,5 +41,6 @@ class Database(AppwriteModel): enabled: bool = Field(..., alias='enabled') type: DatabaseType = Field(..., alias='type') status: Optional[DatabaseStatus] = Field(default=None, alias='status') - policies: List[BackupPolicy] = Field(..., alias='policies') - archives: List[BackupArchive] = Field(..., alias='archives') + replicas: Optional[float] = Field(default=None, alias='replicas') + policies: Optional[List[BackupPolicy]] = Field(default=None, alias='policies') + archives: Optional[List[BackupArchive]] = Field(default=None, alias='archives') diff --git a/appwrite/models/database_status.py b/appwrite/models/database_status.py new file mode 100644 index 00000000..dd469917 --- /dev/null +++ b/appwrite/models/database_status.py @@ -0,0 +1,39 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .database_status_connections import DatabaseStatusConnections +from .database_status_replica import DatabaseStatusReplica +from .database_status_volume import DatabaseStatusVolume + +class DatabaseStatus(AppwriteModel): + """ + Status + + Attributes + ---------- + health : str + Overall health status: healthy, degraded, or unhealthy. + ready : bool + Whether the database is ready to accept connections. + engine : str + Database engine: postgresql, mysql, mariadb, or mongodb. + version : str + Database engine version. + uptime : float + Database uptime in seconds. + connections : DatabaseStatusConnections + Connection statistics. + replicas : List[DatabaseStatusReplica] + List of database replicas and their status. + volumes : List[DatabaseStatusVolume] + Storage volume information. + """ + health: str = Field(..., alias='health') + ready: bool = Field(..., alias='ready') + engine: str = Field(..., alias='engine') + version: str = Field(..., alias='version') + uptime: float = Field(..., alias='uptime') + connections: DatabaseStatusConnections = Field(..., alias='connections') + replicas: List[DatabaseStatusReplica] = Field(..., alias='replicas') + volumes: List[DatabaseStatusVolume] = Field(..., alias='volumes') diff --git a/appwrite/models/database_status_connections.py b/appwrite/models/database_status_connections.py new file mode 100644 index 00000000..fe335596 --- /dev/null +++ b/appwrite/models/database_status_connections.py @@ -0,0 +1,18 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class DatabaseStatusConnections(AppwriteModel): + """ + Connections + + Attributes + ---------- + current : float + Current number of active connections. + max : float + Maximum allowed connections. + """ + current: float = Field(..., alias='current') + max: float = Field(..., alias='max') diff --git a/appwrite/models/database_status_replica.py b/appwrite/models/database_status_replica.py new file mode 100644 index 00000000..73cf79e4 --- /dev/null +++ b/appwrite/models/database_status_replica.py @@ -0,0 +1,24 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class DatabaseStatusReplica(AppwriteModel): + """ + Replica + + Attributes + ---------- + index : float + StatefulSet pod index (0 = primary, 1+ = replicas). + role : str + Replica role: primary or replica. + healthy : bool + Whether the replica is healthy. + lagseconds : Optional[float] + Replication lag in seconds (null for primary). + """ + index: float = Field(..., alias='index') + role: str = Field(..., alias='role') + healthy: bool = Field(..., alias='healthy') + lagseconds: Optional[float] = Field(default=None, alias='lagSeconds') diff --git a/appwrite/models/database_status_volume.py b/appwrite/models/database_status_volume.py new file mode 100644 index 00000000..0b14ba8c --- /dev/null +++ b/appwrite/models/database_status_volume.py @@ -0,0 +1,24 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class DatabaseStatusVolume(AppwriteModel): + """ + Volume + + Attributes + ---------- + path : str + Mount path of the volume. + usedpercent : str + Percentage of storage used. + available : str + Available storage space. + mounted : bool + Whether the volume is mounted. + """ + path: str = Field(..., alias='path') + usedpercent: str = Field(..., alias='usedPercent') + available: str = Field(..., alias='available') + mounted: bool = Field(..., alias='mounted') diff --git a/appwrite/models/dedicated_database.py b/appwrite/models/dedicated_database.py new file mode 100644 index 00000000..9f943228 --- /dev/null +++ b/appwrite/models/dedicated_database.py @@ -0,0 +1,159 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class DedicatedDatabase(AppwriteModel): + """ + DedicatedDatabase + + Attributes + ---------- + id : str + Dedicated database ID. + createdat : str + Database creation time in ISO 8601 format. + updatedat : str + Database update date in ISO 8601 format. + projectid : str + Project ID that owns this database. + name : str + Database display name. + api : str + Product API that owns this database: tablesdb, documentsdb, vectorsdb, mysql, postgresql, or mongodb. + engine : str + Database engine: postgresql, mysql, mariadb, or mongodb. + version : str + Database engine version. + specification : str + Specification identifier. + backend : str + Database backend provider. Possible values: prisma, edge. + hostname : str + Database hostname for connections. + connectionport : float + Database port for connections. + connectionuser : str + Database username for connections. + connectionpassword : str + Database password for connections. + connectionstring : str + Full database connection string (URI format). + ssl : bool + Whether SSL/TLS is required for client connections. + status : str + Database status. Possible values: provisioning, ready, inactive, paused, failed, deleted, restoring, scaling. + containerstatus : str + Container status for lifecycle-managed database runtimes: active or inactive. + lastaccessedat : Optional[str] + Last activity timestamp in ISO 8601 format. + idleuntil : Optional[str] + Display-only timestamp when the database is expected to be considered idle (ISO 8601 format). Derived from last activity; lifecycle transitions are driven by lifecycleState. + lifecyclestate : str + Idle-lifecycle state of the database. Possible values: active, warm, cold, hibernated. + idletimeoutminutes : float + Minutes of inactivity before container scales to zero. + cpu : float + CPU allocated in millicores. + memory : float + Memory allocated in MB. + storage : float + Storage allocated in GB. + storageclass : str + Storage class. Currently always 'ssd'; DigitalOcean exposes a single block-storage class. + storagemaxgb : float + Maximum storage allowed in GB. 0 means use system default. + nodepool : str + Kubernetes node pool where the database is scheduled. + replicas : float + Number of high availability replicas. High availability is enabled when greater than 0. + syncmode : str + Replication sync mode: async, sync, or quorum. + crossregionreplicas : float + Number of cross-region replicas. Cross-region availability is enabled when greater than 0. + networkmaxconnections : float + Maximum concurrent connections. + networkidletimeoutseconds : float + Connection idle timeout in seconds. + networkipallowlist : List[Any] + IP addresses/CIDR ranges allowed to connect. + backupenabled : bool + Whether automatic backups are enabled. + pitr : bool + Whether point-in-time recovery is enabled. + pitrretentiondays : float + Number of days to retain PITR data. + storageautoscaling : bool + Whether automatic storage expansion is enabled. + storageautoscalingthresholdpercent : float + Storage usage percentage that triggers automatic expansion. + storageautoscalingmaxgb : float + Maximum storage size in GB for autoscaling. 0 means no limit. + maintenancewindowday : str + Day of the week for the maintenance window. Possible values: sun, mon, tue, wed, thu, fri, sat. + maintenancewindowhourutc : float + Hour in UTC (0-23) when the maintenance window starts. + metricsenabled : bool + Whether metrics collection is enabled. + sqlapienabled : bool + Whether the SQL API sidecar is enabled for this database. + sqlapiallowedstatements : List[Any] + Statement types accepted by the SQL API. Defaults to read/write DML only; DDL/DCL types (CREATE, ALTER, DROP, TRUNCATE, GRANT, REVOKE) are opt-in per database. Allowed values: SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, TRUNCATE, GRANT, REVOKE. + sqlapimaxrows : float + Maximum rows returned per SQL API execution. Results larger than this are truncated. + sqlapimaxbytes : float + Maximum serialised SQL API result payload in bytes. Results larger than this are truncated. + sqlapitimeoutseconds : float + Maximum server-side SQL API execution time in seconds before the query is cancelled. + error : str + Error message if status is failed. + """ + id: str = Field(..., alias='$id') + createdat: str = Field(..., alias='$createdAt') + updatedat: str = Field(..., alias='$updatedAt') + projectid: str = Field(..., alias='projectId') + name: str = Field(..., alias='name') + api: str = Field(..., alias='api') + engine: str = Field(..., alias='engine') + version: str = Field(..., alias='version') + specification: str = Field(..., alias='specification') + backend: str = Field(..., alias='backend') + hostname: str = Field(..., alias='hostname') + connectionport: float = Field(..., alias='connectionPort') + connectionuser: str = Field(..., alias='connectionUser') + connectionpassword: str = Field(..., alias='connectionPassword') + connectionstring: str = Field(..., alias='connectionString') + ssl: bool = Field(..., alias='ssl') + status: str = Field(..., alias='status') + containerstatus: str = Field(..., alias='containerStatus') + lastaccessedat: Optional[str] = Field(default=None, alias='lastAccessedAt') + idleuntil: Optional[str] = Field(default=None, alias='idleUntil') + lifecyclestate: str = Field(..., alias='lifecycleState') + idletimeoutminutes: float = Field(..., alias='idleTimeoutMinutes') + cpu: float = Field(..., alias='cpu') + memory: float = Field(..., alias='memory') + storage: float = Field(..., alias='storage') + storageclass: str = Field(..., alias='storageClass') + storagemaxgb: float = Field(..., alias='storageMaxGb') + nodepool: str = Field(..., alias='nodePool') + replicas: float = Field(..., alias='replicas') + syncmode: str = Field(..., alias='syncMode') + crossregionreplicas: float = Field(..., alias='crossRegionReplicas') + networkmaxconnections: float = Field(..., alias='networkMaxConnections') + networkidletimeoutseconds: float = Field(..., alias='networkIdleTimeoutSeconds') + networkipallowlist: List[Any] = Field(..., alias='networkIPAllowlist') + backupenabled: bool = Field(..., alias='backupEnabled') + pitr: bool = Field(..., alias='pitr') + pitrretentiondays: float = Field(..., alias='pitrRetentionDays') + storageautoscaling: bool = Field(..., alias='storageAutoscaling') + storageautoscalingthresholdpercent: float = Field(..., alias='storageAutoscalingThresholdPercent') + storageautoscalingmaxgb: float = Field(..., alias='storageAutoscalingMaxGb') + maintenancewindowday: str = Field(..., alias='maintenanceWindowDay') + maintenancewindowhourutc: float = Field(..., alias='maintenanceWindowHourUtc') + metricsenabled: bool = Field(..., alias='metricsEnabled') + sqlapienabled: bool = Field(..., alias='sqlApiEnabled') + sqlapiallowedstatements: List[Any] = Field(..., alias='sqlApiAllowedStatements') + sqlapimaxrows: float = Field(..., alias='sqlApiMaxRows') + sqlapimaxbytes: float = Field(..., alias='sqlApiMaxBytes') + sqlapitimeoutseconds: float = Field(..., alias='sqlApiTimeoutSeconds') + error: str = Field(..., alias='error') diff --git a/appwrite/models/dedicated_database_member.py b/appwrite/models/dedicated_database_member.py new file mode 100644 index 00000000..6cb4daa3 --- /dev/null +++ b/appwrite/models/dedicated_database_member.py @@ -0,0 +1,24 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class DedicatedDatabaseMember(AppwriteModel): + """ + Member + + Attributes + ---------- + id : str + Member identifier. + role : str + Member role. Possible values: primary (accepts reads and writes), replica (read-only follower). + status : str + Member pod status. Possible values: provisioning (pod missing or Pending), starting (Running but not Ready), active (Running and Ready), failed (Failed phase or CrashLoopBackOff container), or the lowercased pod phase reported by the cluster. + lagseconds : float + Replication lag in seconds. + """ + id: str = Field(..., alias='$id') + role: str = Field(..., alias='role') + status: str = Field(..., alias='status') + lagseconds: float = Field(..., alias='lagSeconds') diff --git a/appwrite/models/dedicated_database_replicas.py b/appwrite/models/dedicated_database_replicas.py new file mode 100644 index 00000000..fa19a87a --- /dev/null +++ b/appwrite/models/dedicated_database_replicas.py @@ -0,0 +1,22 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .dedicated_database_member import DedicatedDatabaseMember + +class DedicatedDatabaseReplicas(AppwriteModel): + """ + Replicas + + Attributes + ---------- + replicas : float + Number of configured replicas. Zero means high availability is disabled. + syncmode : str + Replication sync mode. Possible values: async (asynchronous, fastest), sync (synchronous, strong consistency), quorum (quorum-based, majority of replicas must confirm). + members : List[DedicatedDatabaseMember] + Per-pod statuses for the primary and every replica. + """ + replicas: float = Field(..., alias='replicas') + syncmode: str = Field(..., alias='syncMode') + members: List[DedicatedDatabaseMember] = Field(..., alias='members') diff --git a/appwrite/models/dedicated_database_specification.py b/appwrite/models/dedicated_database_specification.py new file mode 100644 index 00000000..d5f37295 --- /dev/null +++ b/appwrite/models/dedicated_database_specification.py @@ -0,0 +1,39 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class DedicatedDatabaseSpecification(AppwriteModel): + """ + Specification + + Attributes + ---------- + slug : str + Specification slug. Use this value when creating a dedicated database. + name : str + Human readable specification name. + price : float + Monthly price of the specification in USD. + cpu : float + Allocated CPU in millicores. + memory : float + Allocated memory in MB. + maxconnections : float + Maximum number of concurrent connections. + includedstorage : float + Included storage in GB before overage charges apply. + includedbandwidth : float + Included bandwidth in GB before overage charges apply. + enabled : bool + Whether the specification is available on the current plan. + """ + slug: str = Field(..., alias='slug') + name: str = Field(..., alias='name') + price: float = Field(..., alias='price') + cpu: float = Field(..., alias='cpu') + memory: float = Field(..., alias='memory') + maxconnections: float = Field(..., alias='maxConnections') + includedstorage: float = Field(..., alias='includedStorage') + includedbandwidth: float = Field(..., alias='includedBandwidth') + enabled: bool = Field(..., alias='enabled') diff --git a/appwrite/models/dedicated_database_specification_list.py b/appwrite/models/dedicated_database_specification_list.py new file mode 100644 index 00000000..8cbd905d --- /dev/null +++ b/appwrite/models/dedicated_database_specification_list.py @@ -0,0 +1,23 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .dedicated_database_specification import DedicatedDatabaseSpecification +from .dedicated_database_specification_pricing import DedicatedDatabaseSpecificationPricing + +class DedicatedDatabaseSpecificationList(AppwriteModel): + """ + SpecificationList + + Attributes + ---------- + specifications : List[DedicatedDatabaseSpecification] + List of dedicated database specifications. + total : float + Total number of specifications. + pricing : DedicatedDatabaseSpecificationPricing + Overage and add-on pricing shared across all specifications. + """ + specifications: List[DedicatedDatabaseSpecification] = Field(..., alias='specifications') + total: float = Field(..., alias='total') + pricing: DedicatedDatabaseSpecificationPricing = Field(..., alias='pricing') diff --git a/appwrite/models/dedicated_database_specification_pricing.py b/appwrite/models/dedicated_database_specification_pricing.py new file mode 100644 index 00000000..43d3b1e7 --- /dev/null +++ b/appwrite/models/dedicated_database_specification_pricing.py @@ -0,0 +1,27 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class DedicatedDatabaseSpecificationPricing(AppwriteModel): + """ + SpecificationPricing + + Attributes + ---------- + storageoveragerate : float + Price per GB of storage above the included amount, per month, in USD. + bandwidthoveragerate : float + Price per GB of bandwidth above the included amount, per month, in USD. + replicarate : float + High availability replica price as a fraction of the specification cost. + crossregionreplicarate : float + Cross-region replica price as a fraction of the specification cost. + pitrrate : float + Point-in-time recovery price as a fraction of the specification cost. + """ + storageoveragerate: float = Field(..., alias='storageOverageRate') + bandwidthoveragerate: float = Field(..., alias='bandwidthOverageRate') + replicarate: float = Field(..., alias='replicaRate') + crossregionreplicarate: float = Field(..., alias='crossRegionReplicaRate') + pitrrate: float = Field(..., alias='pitrRate') diff --git a/appwrite/models/oauth2_consent.py b/appwrite/models/oauth2_consent.py new file mode 100644 index 00000000..6675856b --- /dev/null +++ b/appwrite/models/oauth2_consent.py @@ -0,0 +1,42 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class Oauth2Consent(AppwriteModel): + """ + OAuth2 Consent + + Attributes + ---------- + id : str + Consent ID. + createdat : str + Consent creation time in ISO 8601 format. + updatedat : str + Consent update date in ISO 8601 format. + userid : str + ID of the user the consent belongs to. + appid : str + ID of the registered app the consent was given to. Empty for URL-form (CIMD) clients. + cimdurl : str + Client ID metadata document URL of the client the consent was given to. Empty for registered apps. + scopes : List[Any] + OAuth2 scopes the user consented to. + resources : List[Any] + RFC 8707 resource indicators the user consented to. + authorizationdetails : str + Authorization details the user consented to, as a JSON string. Each entry has a `type` plus project-defined fields. + expire : str + Consent expiration time in ISO 8601 format. Empty when the consent has no token-bound expiry yet. + """ + id: str = Field(..., alias='$id') + createdat: str = Field(..., alias='$createdAt') + updatedat: str = Field(..., alias='$updatedAt') + userid: str = Field(..., alias='userId') + appid: str = Field(..., alias='appId') + cimdurl: str = Field(..., alias='cimdUrl') + scopes: List[Any] = Field(..., alias='scopes') + resources: List[Any] = Field(..., alias='resources') + authorizationdetails: str = Field(..., alias='authorizationDetails') + expire: str = Field(..., alias='expire') diff --git a/appwrite/models/oauth2_consent_list.py b/appwrite/models/oauth2_consent_list.py new file mode 100644 index 00000000..610353b6 --- /dev/null +++ b/appwrite/models/oauth2_consent_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .oauth2_consent import Oauth2Consent + +class Oauth2ConsentList(AppwriteModel): + """ + OAuth2 consents list + + Attributes + ---------- + total : float + Total number of consents that matched your query. + consents : List[Oauth2Consent] + List of consents. + """ + total: float = Field(..., alias='total') + consents: List[Oauth2Consent] = Field(..., alias='consents') diff --git a/appwrite/models/oauth2_consent_token.py b/appwrite/models/oauth2_consent_token.py new file mode 100644 index 00000000..2fd26ca5 --- /dev/null +++ b/appwrite/models/oauth2_consent_token.py @@ -0,0 +1,45 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class Oauth2ConsentToken(AppwriteModel): + """ + OAuth2 Consent Token + + Attributes + ---------- + id : str + Token family ID. + createdat : str + Token creation time in ISO 8601 format. + updatedat : str + Token update date in ISO 8601 format. Refreshing the token family updates this. + consentid : str + ID of the consent the token family was issued under. + userid : str + ID of the user the token family belongs to. + appid : str + ID of the registered app the token family was issued to. Empty for URL-form (CIMD) clients. + cimdurl : str + Client ID metadata document URL of the client the token family was issued to. Empty for registered apps. + scopes : List[Any] + OAuth2 scopes granted on the token family. + resources : List[Any] + RFC 8707 resource indicators granted on the token family. + authorizationdetails : str + Authorization details granted on the token family, as a JSON string. Each entry has a `type` plus project-defined fields. + expire : str + Expiration time of the current access token of this family in ISO 8601 format. + """ + id: str = Field(..., alias='$id') + createdat: str = Field(..., alias='$createdAt') + updatedat: str = Field(..., alias='$updatedAt') + consentid: str = Field(..., alias='consentId') + userid: str = Field(..., alias='userId') + appid: str = Field(..., alias='appId') + cimdurl: str = Field(..., alias='cimdUrl') + scopes: List[Any] = Field(..., alias='scopes') + resources: List[Any] = Field(..., alias='resources') + authorizationdetails: str = Field(..., alias='authorizationDetails') + expire: str = Field(..., alias='expire') diff --git a/appwrite/models/oauth2_consent_token_list.py b/appwrite/models/oauth2_consent_token_list.py new file mode 100644 index 00000000..e4864cdb --- /dev/null +++ b/appwrite/models/oauth2_consent_token_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .oauth2_consent_token import Oauth2ConsentToken + +class Oauth2ConsentTokenList(AppwriteModel): + """ + OAuth2 consent tokens list + + Attributes + ---------- + total : float + Total number of tokens that matched your query. + tokens : List[Oauth2ConsentToken] + List of tokens. + """ + total: float = Field(..., alias='total') + tokens: List[Oauth2ConsentToken] = Field(..., alias='tokens') diff --git a/appwrite/models/project.py b/appwrite/models/project.py index 38e41404..3dd2ca01 100644 --- a/appwrite/models/project.py +++ b/appwrite/models/project.py @@ -69,6 +69,8 @@ class Project(AppwriteModel): Project blocks information consoleaccessedat : str Last time the project was accessed via console. Used with plan's projectInactivityDays to determine if project is paused. + wafenabled : bool + Whether WAF enforcement is enabled for the project. billinglimits : Optional[BillingLimits] Billing limits reached oauth2serverenabled : Optional[bool] @@ -129,6 +131,7 @@ class Project(AppwriteModel): protocols: List[ProjectProtocol] = Field(..., alias='protocols') blocks: List[Block] = Field(..., alias='blocks') consoleaccessedat: str = Field(..., alias='consoleAccessedAt') + wafenabled: bool = Field(..., alias='wafEnabled') billinglimits: Optional[BillingLimits] = Field(default=None, alias='billingLimits') oauth2serverenabled: Optional[bool] = Field(default=None, alias='oAuth2ServerEnabled') oauth2serverauthorizationurl: Optional[str] = Field(default=None, alias='oAuth2ServerAuthorizationUrl') diff --git a/appwrite/services/account.py b/appwrite/services/account.py index 5af533a9..492d9102 100644 --- a/appwrite/services/account.py +++ b/appwrite/services/account.py @@ -4,6 +4,10 @@ from ..exception import AppwriteException from appwrite.utils.deprecated import deprecated from ..models.user import User +from ..models.oauth2_consent_list import Oauth2ConsentList +from ..models.oauth2_consent import Oauth2Consent +from ..models.oauth2_consent_token_list import Oauth2ConsentTokenList +from ..models.oauth2_consent_token import Oauth2ConsentToken from ..models.identity_list import IdentityList from ..models.jwt import Jwt from ..models.log_list import LogList @@ -124,6 +128,269 @@ def create( return User.with_data(response, model_type) + def list_consents( + self, + queries: Optional[List[str]] = None, + total: Optional[bool] = None + ) -> Oauth2ConsentList: + """ + Get a list of the OAuth2 consents the current user has given to third-party apps. + + Parameters + ---------- + queries : Optional[List[str]] + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + total : Optional[bool] + When set to false, the total count returned will be 0 and will not be calculated. + + Returns + ------- + Oauth2ConsentList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/account/consents' + api_params = {} + + if queries is not None: + api_params['queries'] = self._normalize_value(queries) + if total is not None: + api_params['total'] = self._normalize_value(total) + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Oauth2ConsentList) + + + def get_consent( + self, + consent_id: str + ) -> Oauth2Consent: + """ + Get an OAuth2 consent the current user has given to a third-party app by its unique ID. + + Parameters + ---------- + consent_id : str + Consent unique ID. + + Returns + ------- + Oauth2Consent + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/account/consents/{consentId}' + api_params = {} + if consent_id is None: + raise AppwriteException('Missing required parameter: "consent_id"') + + api_path = api_path.replace('{consentId}', str(self._normalize_value(consent_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Oauth2Consent) + + + def delete_consent( + self, + consent_id: str + ) -> Dict[str, Any]: + """ + Delete an OAuth2 consent by its unique ID. All token families issued under the consent are revoked, and the app must ask for consent again to regain access. + + Parameters + ---------- + consent_id : str + Consent unique ID. + + Returns + ------- + Dict[str, Any] + API response as a dictionary + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/account/consents/{consentId}' + api_params = {} + if consent_id is None: + raise AppwriteException('Missing required parameter: "consent_id"') + + api_path = api_path.replace('{consentId}', str(self._normalize_value(consent_id))) + + + response = self.client.call('delete', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return response + + + def list_consent_tokens( + self, + consent_id: str, + queries: Optional[List[str]] = None, + total: Optional[bool] = None + ) -> Oauth2ConsentTokenList: + """ + Get a list of the token families issued under an OAuth2 consent. Each entry represents one authorized device or session; the token secrets themselves are never returned. + + Parameters + ---------- + consent_id : str + Consent unique ID. + queries : Optional[List[str]] + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + total : Optional[bool] + When set to false, the total count returned will be 0 and will not be calculated. + + Returns + ------- + Oauth2ConsentTokenList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/account/consents/{consentId}/tokens' + api_params = {} + if consent_id is None: + raise AppwriteException('Missing required parameter: "consent_id"') + + api_path = api_path.replace('{consentId}', str(self._normalize_value(consent_id))) + + if queries is not None: + api_params['queries'] = self._normalize_value(queries) + if total is not None: + api_params['total'] = self._normalize_value(total) + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Oauth2ConsentTokenList) + + + def get_consent_token( + self, + consent_id: str, + token_id: str + ) -> Oauth2ConsentToken: + """ + Get a token family issued under an OAuth2 consent by its unique ID. The token secrets themselves are never returned. + + Parameters + ---------- + consent_id : str + Consent unique ID. + token_id : str + Token unique ID. + + Returns + ------- + Oauth2ConsentToken + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/account/consents/{consentId}/tokens/{tokenId}' + api_params = {} + if consent_id is None: + raise AppwriteException('Missing required parameter: "consent_id"') + + if token_id is None: + raise AppwriteException('Missing required parameter: "token_id"') + + api_path = api_path.replace('{consentId}', str(self._normalize_value(consent_id))) + api_path = api_path.replace('{tokenId}', str(self._normalize_value(token_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Oauth2ConsentToken) + + + def delete_consent_token( + self, + consent_id: str, + token_id: str + ) -> Dict[str, Any]: + """ + Delete a token family issued under an OAuth2 consent by its unique ID. The access and refresh tokens of the family stop working immediately; other token families and the consent itself are unaffected. + + Parameters + ---------- + consent_id : str + Consent unique ID. + token_id : str + Token unique ID. + + Returns + ------- + Dict[str, Any] + API response as a dictionary + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/account/consents/{consentId}/tokens/{tokenId}' + api_params = {} + if consent_id is None: + raise AppwriteException('Missing required parameter: "consent_id"') + + if token_id is None: + raise AppwriteException('Missing required parameter: "token_id"') + + api_path = api_path.replace('{consentId}', str(self._normalize_value(consent_id))) + api_path = api_path.replace('{tokenId}', str(self._normalize_value(token_id))) + + + response = self.client.call('delete', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return response + + def update_email( self, email: str, diff --git a/appwrite/services/apps.py b/appwrite/services/apps.py index ab3e9110..8b8e3285 100644 --- a/appwrite/services/apps.py +++ b/appwrite/services/apps.py @@ -5,6 +5,7 @@ from appwrite.utils.deprecated import deprecated from ..models.apps_list import AppsList from ..models.app import App +from ..models.app_scope_list import AppScopeList from ..models.app_secret_list import AppSecretList from ..models.app_secret_plaintext import AppSecretPlaintext from ..models.app_secret import AppSecret @@ -88,7 +89,7 @@ def create( name : str Application name. redirect_uris : List[str] - Redirect URIs (array of valid URLs). + Redirect URIs. Each must be an https URL, an http loopback URL (localhost, 127.0.0.1, [::1]), or a private-use scheme URI (e.g. com.example.app:/oauth), and must not contain a fragment. description : Optional[str] Application description shown to users during OAuth2 consent. client_uri : Optional[str] @@ -112,7 +113,7 @@ def create( data_deletion_url : Optional[str] Application data deletion URL shown to users during OAuth2 consent. post_logout_redirect_uris : Optional[List[str]] - Post-logout redirect URIs for OpenID Connect RP-Initiated Logout (array of valid URLs). After ending the user session, the logout endpoint only redirects to URIs in this list. + Post-logout redirect URIs for OpenID Connect RP-Initiated Logout. Each must be an https URL, an http loopback URL, or a private-use scheme URI, and must not contain a fragment. After ending the user session, the logout endpoint only redirects to URIs in this list. enabled : Optional[bool] Is application enabled? type : Optional[str] @@ -190,6 +191,34 @@ def create( return self._parse_response(response, model=App) + def list_o_auth2_scopes( + self + ) -> AppScopeList: + """ + List scopes an application can request during the OAuth2 flow. + + Returns + ------- + AppScopeList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/scopes/oauth2' + api_params = {} + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppScopeList) + + def get( self, app_id: str @@ -200,7 +229,7 @@ def get( Parameters ---------- app_id : str - Application unique ID. + Application unique ID or HTTPS client ID metadata document URL. Returns ------- @@ -284,9 +313,9 @@ def update( enabled : Optional[bool] Is application enabled? redirect_uris : Optional[List[str]] - Redirect URIs (array of valid URLs). + Redirect URIs. Each must be an https URL, an http loopback URL (localhost, 127.0.0.1, [::1]), or a private-use scheme URI (e.g. com.example.app:/oauth), and must not contain a fragment. post_logout_redirect_uris : Optional[List[str]] - Post-logout redirect URIs for OpenID Connect RP-Initiated Logout (array of valid URLs). After ending the user session, the logout endpoint only redirects to URIs in this list. + Post-logout redirect URIs for OpenID Connect RP-Initiated Logout. Each must be an https URL, an http loopback URL, or a private-use scheme URI, and must not contain a fragment. After ending the user session, the logout endpoint only redirects to URIs in this list. type : Optional[str] OAuth2 client type. Use `public` for SPAs, mobile, and native apps that cannot keep a `client_secret` — PKCE is then required at the token endpoint. Use `confidential` for server-side clients that present a `client_secret`. Defaults to `confidential`. device_flow : Optional[bool] diff --git a/appwrite/services/backups.py b/appwrite/services/backups.py index 5aac7329..c8b782c5 100644 --- a/appwrite/services/backups.py +++ b/appwrite/services/backups.py @@ -434,13 +434,18 @@ def create_restoration( archive_id: str, services: List[BackupServices], new_resource_id: Optional[str] = None, - new_resource_name: Optional[str] = None, - new_specification: Optional[str] = None + new_resource_name: Optional[str] = None ) -> BackupRestoration: """ Create and trigger a new restoration for a backup on a project. - When restoring a DocumentsDB or VectorsDB database to a new resource, pass `newSpecification` to provision the restored database on a different specification than the archived one (for example, restoring onto a larger or smaller dedicated database). Use `serverless` to restore onto the shared pool, or a dedicated specification slug to restore onto a dedicated database of that size. The specification must be permitted by the organization's plan. `newSpecification` is not supported for legacy/TablesDB databases or for bucket restores. + For a backup of one database, the restoration resolves its destination before it is queued. Pass `newResourceId` to restore into that database ID, including the archived database ID to overwrite it. When `newResourceId` is omitted, a new database ID is generated and returned in `options`. + + The restoration migration records the archived database in `resourceId` and `resourceType`, and the resolved database in `destinationResourceId` and `destinationResourceType`. Database types are stored canonically as `database`, `documentsdb`, or `vectorsdb`. Project-wide restorations leave these fields empty because they do not have a single source or destination database. + + To list every migration related to one database, use its canonical type in a nested `OR(AND(...), AND(...), AND(...))` across the root, parent, and destination relation pairs: `(resourceType, resourceId)`, `(parentResourceType, parentResourceId)`, and `(destinationResourceType, destinationResourceId)`. Legacy and TablesDB databases use `database`; the operational `resourceType` of a table migration is not rewritten to `tablesdb`. + + When restoring a DocumentsDB or VectorsDB database to a new resource from a dedicated source, the restore provisions a fresh dedicated backing database at the source database's own specification. Parameters @@ -450,11 +455,9 @@ def create_restoration( services : List[BackupServices] Array of services to restore new_resource_id : Optional[str] - Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. + Destination resource ID. Omit to generate a new ID, or pass the archived resource ID to overwrite it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. new_resource_name : Optional[str] Database name. Max length: 128 chars. - new_specification : Optional[str] - Specification to provision the restored database on, when restoring a DocumentsDB or VectorsDB database to a new resource. Defaults to the archived database's specification. Use `serverless` for the shared pool or a dedicated specification slug. Returns ------- @@ -482,8 +485,6 @@ def create_restoration( api_params['newResourceId'] = self._normalize_value(new_resource_id) if new_resource_name is not None: api_params['newResourceName'] = self._normalize_value(new_resource_name) - if new_specification is not None: - api_params['newSpecification'] = self._normalize_value(new_specification) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/oauth2.py b/appwrite/services/oauth2.py index 21612207..cf7f0bc6 100644 --- a/appwrite/services/oauth2.py +++ b/appwrite/services/oauth2.py @@ -91,7 +91,7 @@ def authorize( Parameters ---------- client_id : Optional[str] - OAuth2 client ID. + OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. redirect_uri : Optional[str] Redirect URI where visitor will be redirected after authorization, whether successful or not. response_type : Optional[str] @@ -170,6 +170,109 @@ def authorize( return self._parse_response(response, model=Oauth2Authorize) + def authorize_post( + self, + client_id: Optional[str] = None, + redirect_uri: Optional[str] = None, + response_type: Optional[str] = None, + scope: Optional[str] = None, + state: Optional[str] = None, + nonce: Optional[str] = None, + code_challenge: Optional[str] = None, + code_challenge_method: Optional[str] = None, + prompt: Optional[str] = None, + max_age: Optional[float] = None, + authorization_details: Optional[str] = None, + resource: Optional[str] = None, + audience: Optional[str] = None, + request_uri: Optional[str] = None + ) -> Oauth2Authorize: + """ + Begin the OAuth2 authorization flow. When called without a session, the user is redirected to the consent screen without grant ID. When called with a session, the redirect URL includes param for grant ID. You can pass Accept header of `application/json` to receive a JSON response instead of a redirect. + + Parameters + ---------- + client_id : Optional[str] + OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. + redirect_uri : Optional[str] + Redirect URI where visitor will be redirected after authorization, whether successful or not. + response_type : Optional[str] + OAuth2 / OIDC response type. One of `code` (Authorization Code Flow), `id_token` (Implicit Flow, OIDC login only), or `code id_token` (Hybrid Flow). + scope : Optional[str] + Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`, `phone`. + state : Optional[str] + OAuth2 state. You receive this back in the redirect URI. + nonce : Optional[str] + OIDC nonce parameter to prevent replay attacks. Required when response_type includes `id_token`. + code_challenge : Optional[str] + PKCE code challenge. Required when OAuth2 app is public. + code_challenge_method : Optional[str] + PKCE code challenge method. Required when OAuth2 app is public. + prompt : Optional[str] + OIDC prompt parameter for customization of consent screen. Space-separated list of: none, login, consent, select_account. + max_age : Optional[float] + OIDC max_age paraleter for customization of consent screen. Maximum allowable elapsed time in seconds since the user last authenticated. If exceeded, re-authentication is required. + authorization_details : Optional[str] + Rich authorization request. JSON array of objects, each with a `type` and project-defined fields + resource : Optional[str] + RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment. + audience : Optional[str] + Compatibility alias for a single OAuth2 resource indicator URI. + request_uri : Optional[str] + OAuth2 authorization request handle returned by the pushed authorization request endpoint. + + Returns + ------- + Oauth2Authorize + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/oauth2/{project_id}/authorize' + api_params = {} + api_path = api_path.replace('{project_id}', str(self._normalize_value(self.client.get_config('project')))) + + if client_id is not None: + api_params['client_id'] = self._normalize_value(client_id) + if redirect_uri is not None: + api_params['redirect_uri'] = self._normalize_value(redirect_uri) + if response_type is not None: + api_params['response_type'] = self._normalize_value(response_type) + if scope is not None: + api_params['scope'] = self._normalize_value(scope) + if state is not None: + api_params['state'] = self._normalize_value(state) + if nonce is not None: + api_params['nonce'] = self._normalize_value(nonce) + if code_challenge is not None: + api_params['code_challenge'] = self._normalize_value(code_challenge) + if code_challenge_method is not None: + api_params['code_challenge_method'] = self._normalize_value(code_challenge_method) + if prompt is not None: + api_params['prompt'] = self._normalize_value(prompt) + if max_age is not None: + api_params['max_age'] = self._normalize_value(max_age) + if authorization_details is not None: + api_params['authorization_details'] = self._normalize_value(authorization_details) + if resource is not None: + api_params['resource'] = self._normalize_value(resource) + if audience is not None: + api_params['audience'] = self._normalize_value(audience) + if request_uri is not None: + api_params['request_uri'] = self._normalize_value(request_uri) + + response = self.client.call('post', api_path, { + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Oauth2Authorize) + + def create_device_authorization( self, client_id: Optional[str] = None, @@ -184,7 +287,7 @@ def create_device_authorization( Parameters ---------- client_id : Optional[str] - OAuth2 client ID. + OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. scope : Optional[str] Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: `openid`, `email`, `profile`. authorization_details : Optional[str] @@ -376,7 +479,7 @@ def create_par( Parameters ---------- client_id : str - OAuth2 client ID. + OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. redirect_uri : str Redirect URI where visitor will be redirected after authorization, whether successful or not. response_type : str @@ -562,7 +665,7 @@ def revoke( token_type_hint : Optional[str] Type of token to revoke (access_token or refresh_token). client_id : Optional[str] - OAuth2 client ID. + OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. client_secret : Optional[str] OAuth2 client secret. Required for confidential apps; omitted for public apps. @@ -627,7 +730,7 @@ def create_token( device_code : Optional[str] Device code obtained from the device authorization endpoint. Required for `urn:ietf:params:oauth:grant-type:device_code` grant type. client_id : Optional[str] - OAuth2 client ID. + OAuth2 client ID. Either a registered app ID or an HTTPS client ID metadata document URL. client_secret : Optional[str] OAuth2 client secret. Required for confidential apps. code_verifier : Optional[str] diff --git a/appwrite/services/sites.py b/appwrite/services/sites.py index 611dcb02..bcf0c1ad 100644 --- a/appwrite/services/sites.py +++ b/appwrite/services/sites.py @@ -988,7 +988,8 @@ def get_deployment_download( self, site_id: str, deployment_id: str, - type: Optional[DeploymentDownloadType] = None + type: Optional[DeploymentDownloadType] = None, + token: Optional[str] = None ) -> bytes: """ Get a site deployment content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory. @@ -1001,6 +1002,8 @@ def get_deployment_download( Deployment ID. type : Optional[DeploymentDownloadType] Deployment file to download. Can be: "source", "output". + token : Optional[str] + Presigned source-download token for accessing this deployment without a session (jobs-service). Returns ------- @@ -1026,6 +1029,8 @@ def get_deployment_download( if type is not None: api_params['type'] = self._normalize_value(type) + if token is not None: + api_params['token'] = self._normalize_value(token) response = self.client.call('get', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), diff --git a/appwrite/services/tables_db.py b/appwrite/services/tables_db.py index 806bdc84..f84f9cca 100644 --- a/appwrite/services/tables_db.py +++ b/appwrite/services/tables_db.py @@ -5,8 +5,12 @@ from appwrite.utils.deprecated import deprecated from ..models.database_list import DatabaseList from ..models.database import Database +from ..models.dedicated_database_specification_list import DedicatedDatabaseSpecificationList from ..models.transaction_list import TransactionList from ..models.transaction import Transaction +from ..models.dedicated_database import DedicatedDatabase +from ..models.dedicated_database_replicas import DedicatedDatabaseReplicas +from ..models.database_status import DatabaseStatus from ..models.table_list import TableList from ..models.table import Table from ..models.column_list import ColumnList @@ -96,7 +100,8 @@ def create( database_id: str, name: str, enabled: Optional[bool] = None, - specification: Optional[str] = None + specification: Optional[str] = None, + replicas: Optional[float] = None ) -> Database: """ Create a new Database. @@ -112,6 +117,8 @@ def create( Is the database enabled? When set to 'disabled', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled. specification : Optional[str] Database specification. Defaults to `serverless`, which creates the database on the shared pool. Any other value provisions a dedicated database on that specification. + replicas : Optional[float] + Number of high availability replicas (0-5) for the dedicated database backing this database. Requires a dedicated `specification`; must be 0 for a serverless database. High availability is enabled when greater than 0. Returns ------- @@ -139,6 +146,8 @@ def create( api_params['enabled'] = self._normalize_value(enabled) if specification is not None: api_params['specification'] = self._normalize_value(specification) + if replicas is not None: + api_params['replicas'] = self._normalize_value(replicas) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -149,6 +158,34 @@ def create( return self._parse_response(response, model=Database) + def list_specifications( + self + ) -> DedicatedDatabaseSpecificationList: + """ + List the dedicated database specifications available on the current plan. Each specification reports its resource limits, pricing, and whether it is enabled for the organization. + + Returns + ------- + DedicatedDatabaseSpecificationList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/tablesdb/specifications' + api_params = {} + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=DedicatedDatabaseSpecificationList) + + def list_transactions( self, queries: Optional[List[str]] = None @@ -440,7 +477,8 @@ def update( self, database_id: str, name: Optional[str] = None, - enabled: Optional[bool] = None + enabled: Optional[bool] = None, + replicas: Optional[float] = None ) -> Database: """ Update a database by its unique ID. @@ -453,6 +491,8 @@ def update( Database name. Max length: 128 chars. enabled : Optional[bool] Is database enabled? When set to 'disabled', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled. + replicas : Optional[float] + Number of high availability replicas (0-5) for the dedicated database backing this database. Only valid when the database is backed by a dedicated specification. High availability is enabled when greater than 0. Returns ------- @@ -476,6 +516,8 @@ def update( api_params['name'] = self._normalize_value(name) if enabled is not None: api_params['enabled'] = self._normalize_value(enabled) + if replicas is not None: + api_params['replicas'] = self._normalize_value(replicas) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -525,6 +567,129 @@ def delete( return response + def create_failover( + self, + database_id: str, + target_replica_id: Optional[str] = None + ) -> DedicatedDatabase: + """ + Trigger a manual failover for a dedicated database with high availability enabled. Promotes a replica to primary. The failover runs asynchronously; poll the database document for status updates. + + Parameters + ---------- + database_id : str + Database ID. + target_replica_id : Optional[str] + Target replica ID to promote. If not specified, the healthiest replica is selected. + + Returns + ------- + DedicatedDatabase + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/tablesdb/{databaseId}/failovers' + api_params = {} + if database_id is None: + raise AppwriteException('Missing required parameter: "database_id"') + + api_path = api_path.replace('{databaseId}', str(self._normalize_value(database_id))) + + if target_replica_id is not None: + api_params['targetReplicaId'] = self._normalize_value(target_replica_id) + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=DedicatedDatabase) + + + def get_replicas( + self, + database_id: str + ) -> DedicatedDatabaseReplicas: + """ + Get high availability status for a dedicated database. Returns replica statuses, replication lag, and sync mode. + + Parameters + ---------- + database_id : str + Database ID. + + Returns + ------- + DedicatedDatabaseReplicas + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/tablesdb/{databaseId}/replicas' + api_params = {} + if database_id is None: + raise AppwriteException('Missing required parameter: "database_id"') + + api_path = api_path.replace('{databaseId}', str(self._normalize_value(database_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=DedicatedDatabaseReplicas) + + + def get_status( + self, + database_id: str + ) -> DatabaseStatus: + """ + Get real-time health and status information for a dedicated database. Returns health status, readiness, uptime, connection info, replica status, and volume information. + + Parameters + ---------- + database_id : str + Database ID. + + Returns + ------- + DatabaseStatus + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/tablesdb/{databaseId}/status' + api_params = {} + if database_id is None: + raise AppwriteException('Missing required parameter: "database_id"') + + api_path = api_path.replace('{databaseId}', str(self._normalize_value(database_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=DatabaseStatus) + + def list_tables( self, database_id: str, diff --git a/docs/examples/account/delete-consent-token.md b/docs/examples/account/delete-consent-token.md new file mode 100644 index 00000000..614b78ee --- /dev/null +++ b/docs/examples/account/delete-consent-token.md @@ -0,0 +1,16 @@ +```python +from appwrite.client import Client +from appwrite.services.account import Account + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +account = Account(client) + +result = account.delete_consent_token( + consent_id = '', + token_id = '' +) +``` diff --git a/docs/examples/account/delete-consent.md b/docs/examples/account/delete-consent.md new file mode 100644 index 00000000..8c2c4113 --- /dev/null +++ b/docs/examples/account/delete-consent.md @@ -0,0 +1,15 @@ +```python +from appwrite.client import Client +from appwrite.services.account import Account + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +account = Account(client) + +result = account.delete_consent( + consent_id = '' +) +``` diff --git a/docs/examples/account/get-consent-token.md b/docs/examples/account/get-consent-token.md new file mode 100644 index 00000000..a5edd0a7 --- /dev/null +++ b/docs/examples/account/get-consent-token.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.account import Account +from appwrite.models import Oauth2ConsentToken + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +account = Account(client) + +result: Oauth2ConsentToken = account.get_consent_token( + consent_id = '', + token_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/account/get-consent.md b/docs/examples/account/get-consent.md new file mode 100644 index 00000000..45ba0a82 --- /dev/null +++ b/docs/examples/account/get-consent.md @@ -0,0 +1,18 @@ +```python +from appwrite.client import Client +from appwrite.services.account import Account +from appwrite.models import Oauth2Consent + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +account = Account(client) + +result: Oauth2Consent = account.get_consent( + consent_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/account/list-consent-tokens.md b/docs/examples/account/list-consent-tokens.md new file mode 100644 index 00000000..3be9fbe3 --- /dev/null +++ b/docs/examples/account/list-consent-tokens.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.account import Account +from appwrite.models import Oauth2ConsentTokenList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +account = Account(client) + +result: Oauth2ConsentTokenList = account.list_consent_tokens( + consent_id = '', + queries = [], # optional + total = False # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/account/list-consents.md b/docs/examples/account/list-consents.md new file mode 100644 index 00000000..d2b5b69c --- /dev/null +++ b/docs/examples/account/list-consents.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.account import Account +from appwrite.models import Oauth2ConsentList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +account = Account(client) + +result: Oauth2ConsentList = account.list_consents( + queries = [], # optional + total = False # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/list-o-auth-2-scopes.md b/docs/examples/apps/list-o-auth-2-scopes.md new file mode 100644 index 00000000..8720e78c --- /dev/null +++ b/docs/examples/apps/list-o-auth-2-scopes.md @@ -0,0 +1,16 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppScopeList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +apps = Apps(client) + +result: AppScopeList = apps.list_o_auth2_scopes() + +print(result.model_dump()) +``` diff --git a/docs/examples/backups/create-restoration.md b/docs/examples/backups/create-restoration.md index 03f3f0a9..e2966caa 100644 --- a/docs/examples/backups/create-restoration.md +++ b/docs/examples/backups/create-restoration.md @@ -15,8 +15,7 @@ result: BackupRestoration = backups.create_restoration( archive_id = '', services = [BackupServices.DATABASES], new_resource_id = '', # optional - new_resource_name = '', # optional - new_specification = 'serverless' # optional + new_resource_name = '' # optional ) print(result.model_dump()) diff --git a/docs/examples/oauth2/authorize-post.md b/docs/examples/oauth2/authorize-post.md new file mode 100644 index 00000000..b6dd6449 --- /dev/null +++ b/docs/examples/oauth2/authorize-post.md @@ -0,0 +1,31 @@ +```python +from appwrite.client import Client +from appwrite.services.oauth2 import Oauth2 +from appwrite.models import Oauth2Authorize + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_session('') # The user session to authenticate with +client.set_project('') # Your project ID + +oauth2 = Oauth2(client) + +result: Oauth2Authorize = oauth2.authorize_post( + client_id = '', # optional + redirect_uri = 'https://example.com', # optional + response_type = '', # optional + scope = '', # optional + state = '', # optional + nonce = '', # optional + code_challenge = '', # optional + code_challenge_method = 's256', # optional + prompt = '', # optional + max_age = 0, # optional + authorization_details = '', # optional + resource = '', # optional + audience = '', # optional + request_uri = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/sites/get-deployment-download.md b/docs/examples/sites/get-deployment-download.md index eae2543c..7d6ae537 100644 --- a/docs/examples/sites/get-deployment-download.md +++ b/docs/examples/sites/get-deployment-download.md @@ -13,6 +13,7 @@ sites = Sites(client) result: bytes = sites.get_deployment_download( site_id = '', deployment_id = '', - type = DeploymentDownloadType.SOURCE # optional + type = DeploymentDownloadType.SOURCE, # optional + token = '' # optional ) ``` diff --git a/docs/examples/tablesdb/create-failover.md b/docs/examples/tablesdb/create-failover.md new file mode 100644 index 00000000..839f8baf --- /dev/null +++ b/docs/examples/tablesdb/create-failover.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import DedicatedDatabase + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result: DedicatedDatabase = tables_db.create_failover( + database_id = '', + target_replica_id = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/tablesdb/create.md b/docs/examples/tablesdb/create.md index a816905f..0c5260cf 100644 --- a/docs/examples/tablesdb/create.md +++ b/docs/examples/tablesdb/create.md @@ -14,7 +14,8 @@ result: Database = tables_db.create( database_id = '', name = '', enabled = False, # optional - specification = 'serverless' # optional + specification = 'serverless', # optional + replicas = 0 # optional ) print(result.model_dump()) diff --git a/docs/examples/tablesdb/get-replicas.md b/docs/examples/tablesdb/get-replicas.md new file mode 100644 index 00000000..abd5df1b --- /dev/null +++ b/docs/examples/tablesdb/get-replicas.md @@ -0,0 +1,18 @@ +```python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import DedicatedDatabaseReplicas + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result: DedicatedDatabaseReplicas = tables_db.get_replicas( + database_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/tablesdb/get-status.md b/docs/examples/tablesdb/get-status.md new file mode 100644 index 00000000..0695da1f --- /dev/null +++ b/docs/examples/tablesdb/get-status.md @@ -0,0 +1,18 @@ +```python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import DatabaseStatus + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result: DatabaseStatus = tables_db.get_status( + database_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/tablesdb/list-specifications.md b/docs/examples/tablesdb/list-specifications.md new file mode 100644 index 00000000..c0fc0f8b --- /dev/null +++ b/docs/examples/tablesdb/list-specifications.md @@ -0,0 +1,16 @@ +```python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import DedicatedDatabaseSpecificationList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result: DedicatedDatabaseSpecificationList = tables_db.list_specifications() + +print(result.model_dump()) +``` diff --git a/docs/examples/tablesdb/update.md b/docs/examples/tablesdb/update.md index aea54858..8609072e 100644 --- a/docs/examples/tablesdb/update.md +++ b/docs/examples/tablesdb/update.md @@ -13,7 +13,8 @@ tables_db = TablesDB(client) result: Database = tables_db.update( database_id = '', name = '', # optional - enabled = False # optional + enabled = False, # optional + replicas = 0 # optional ) print(result.model_dump()) diff --git a/pyproject.toml b/pyproject.toml index 10559426..0d439733 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "appwrite" -version = "22.0.0" +version = "22.1.0" description = "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API" readme = "README.md" requires-python = ">=3.9" diff --git a/setup.py b/setup.py index 9557066e..1baebf1e 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name = 'appwrite', packages = setuptools.find_packages(), - version = '22.0.0', + version = '22.1.0', license='BSD-3-Clause', description = 'Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API', long_description = long_description, @@ -18,7 +18,7 @@ maintainer = 'Appwrite Team', maintainer_email = 'team@appwrite.io', url = 'https://appwrite.io/support', - download_url='https://github.com/appwrite/sdk-for-python/archive/22.0.0.tar.gz', + download_url='https://github.com/appwrite/sdk-for-python/archive/22.1.0.tar.gz', install_requires=[ 'requests', 'pydantic>=2,<3', diff --git a/test/services/test_account.py b/test/services/test_account.py index 4ab80f3b..c0bab652 100644 --- a/test/services/test_account.py +++ b/test/services/test_account.py @@ -72,6 +72,108 @@ def test_create(self, m): self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() + def test_list_consents(self, m): + data = { + "total": 5.0, + "consents": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.account.list_consents( + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_consent(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "userId": "5e5ea5c16897e", + "appId": "5e5ea5c16897e", + "cimdUrl": "https:\/\/example.com\/.well-known\/client-metadata.json", + "scopes": [], + "resources": [], + "authorizationDetails": "[{\"type\":\"calendar\",\"identifier\":\"primary\",\"actions\":[\"read_events\",\"create_event\"]}]", + "expire": "2020-10-15T06:38:00.000+00:00" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.account.get_consent( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_delete_consent(self, m): + data = '' + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.account.delete_consent( + '', + ) + + self.assertEqual(response, data) + + @requests_mock.Mocker() + def test_list_consent_tokens(self, m): + data = { + "total": 5.0, + "tokens": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.account.list_consent_tokens( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_consent_token(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "consentId": "5e5ea5c16897e", + "userId": "5e5ea5c16897e", + "appId": "5e5ea5c16897e", + "cimdUrl": "https:\/\/example.com\/.well-known\/client-metadata.json", + "scopes": [], + "resources": [], + "authorizationDetails": "[{\"type\":\"calendar\",\"identifier\":\"primary\",\"actions\":[\"read_events\",\"create_event\"]}]", + "expire": "2020-10-15T06:38:00.000+00:00" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.account.get_consent_token( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_delete_consent_token(self, m): + data = '' + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.account.delete_consent_token( + '', + '', + ) + + self.assertEqual(response, data) + @requests_mock.Mocker() def test_update_email(self, m): data = { diff --git a/test/services/test_activities.py b/test/services/test_activities.py index 9fcc5e4d..c4b8e550 100644 --- a/test/services/test_activities.py +++ b/test/services/test_activities.py @@ -44,10 +44,21 @@ def test_get_event(self, m): "ip": "127.0.0.1", "mode": "admin", "country": "US", + "continentCode": "NA", + "city": "Mountain View", + "subdivisions": "California", + "isp": "Google", + "autonomousSystemNumber": "15169", + "autonomousSystemOrganization": "GOOGLE", + "connectionType": "cable", + "connectionUsageType": "residential", + "connectionOrganization": "Google LLC", "time": "2020-10-15T06:38:00.000+00:00", "projectId": "610fc2f985ee0", "teamId": "610fc2f985ee0", - "hostname": "appwrite.io" + "hostname": "appwrite.io", + "sdk": "web", + "sdkVersion": "14.0.0" } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) diff --git a/test/services/test_apps.py b/test/services/test_apps.py index 4520a42b..e457b572 100644 --- a/test/services/test_apps.py +++ b/test/services/test_apps.py @@ -66,6 +66,20 @@ def test_create(self, m): self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() + def test_list_o_auth2_scopes(self, m): + data = { + "total": 5.0, + "scopes": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.list_o_auth2_scopes( + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_get(self, m): data = { @@ -234,7 +248,7 @@ def test_get_secret(self, m): "$createdAt": "2020-10-15T06:38:00.000+00:00", "$updatedAt": "2020-10-15T06:38:00.000+00:00", "appId": "5e5ea5c16897e", - "secret": "$argon2i$v=19$m=16,t=2,p=1$MTIzMTIzMTIzMTIzMQ$3\/ZUl3IWERBO2RIm5rHltg", + "secret": "", "hint": "f5c6c7", "createdById": "5e5ea5c16897e", "createdByName": "Walter White" diff --git a/test/services/test_backups.py b/test/services/test_backups.py index dfa1f5a2..83c59835 100644 --- a/test/services/test_backups.py +++ b/test/services/test_backups.py @@ -196,7 +196,7 @@ def test_create_restoration(self, m): "migrationId": "did8jx6ws45jana098ab7", "services": [], "resources": [], - "options": "{databases.database[{oldId, newId, newName, newSpecification}]}" + "options": "{databases.database[{oldId, newId, newName}]}" } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -235,7 +235,7 @@ def test_get_restoration(self, m): "migrationId": "did8jx6ws45jana098ab7", "services": [], "resources": [], - "options": "{databases.database[{oldId, newId, newName, newSpecification}]}" + "options": "{databases.database[{oldId, newId, newName}]}" } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) diff --git a/test/services/test_databases.py b/test/services/test_databases.py index f6bc3112..11f4ecaf 100644 --- a/test/services/test_databases.py +++ b/test/services/test_databases.py @@ -35,9 +35,7 @@ def test_create(self, m): "$createdAt": "2020-10-15T06:38:00.000+00:00", "$updatedAt": "2020-10-15T06:38:00.000+00:00", "enabled": True, - "type": "legacy", - "policies": [], - "archives": [] + "type": "legacy" } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -158,9 +156,7 @@ def test_get(self, m): "$createdAt": "2020-10-15T06:38:00.000+00:00", "$updatedAt": "2020-10-15T06:38:00.000+00:00", "enabled": True, - "type": "legacy", - "policies": [], - "archives": [] + "type": "legacy" } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -179,9 +175,7 @@ def test_update(self, m): "$createdAt": "2020-10-15T06:38:00.000+00:00", "$updatedAt": "2020-10-15T06:38:00.000+00:00", "enabled": True, - "type": "legacy", - "policies": [], - "archives": [] + "type": "legacy" } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) diff --git a/test/services/test_oauth2.py b/test/services/test_oauth2.py index 3b0bc38e..0f331808 100644 --- a/test/services/test_oauth2.py +++ b/test/services/test_oauth2.py @@ -41,6 +41,20 @@ def test_authorize(self, m): self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() + def test_authorize_post(self, m): + data = { + "grantId": "5e5ea5c16897e", + "redirectUrl": "https:\/\/example.com\/callback?code=abcde&state=fghij" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.oauth2.authorize_post( + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_create_device_authorization(self, m): data = { diff --git a/test/services/test_organization.py b/test/services/test_organization.py index 7cd67f61..399dd374 100644 --- a/test/services/test_organization.py +++ b/test/services/test_organization.py @@ -39,6 +39,7 @@ def test_get(self, m): "screenshotsGenerated": 50.0, "members": 25.0, "webhooks": 25.0, + "wafRules": 2.0, "projects": 2.0, "platforms": 3.0, "users": 25.0, @@ -264,6 +265,7 @@ def test_update(self, m): "screenshotsGenerated": 50.0, "members": 25.0, "webhooks": 25.0, + "wafRules": 2.0, "projects": 2.0, "platforms": 3.0, "users": 25.0, @@ -725,7 +727,8 @@ def test_create_project(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -766,7 +769,8 @@ def test_get_project(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -806,7 +810,8 @@ def test_update_project(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) diff --git a/test/services/test_project.py b/test/services/test_project.py index c3e87cf3..b4b9c640 100644 --- a/test/services/test_project.py +++ b/test/services/test_project.py @@ -42,7 +42,8 @@ def test_get(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -92,7 +93,8 @@ def test_update_auth_method(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -252,7 +254,8 @@ def test_update_labels(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -385,7 +388,8 @@ def test_update_o_auth2_server(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1417,7 +1421,8 @@ def test_update_deny_aliased_email_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1457,7 +1462,8 @@ def test_update_deny_corporate_email_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1497,7 +1503,8 @@ def test_update_deny_disposable_email_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1537,7 +1544,8 @@ def test_update_deny_free_email_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1577,7 +1585,8 @@ def test_update_membership_privacy_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1616,7 +1625,8 @@ def test_update_password_dictionary_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1656,7 +1666,8 @@ def test_update_password_history_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1696,7 +1707,8 @@ def test_update_password_personal_data_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1754,7 +1766,8 @@ def test_update_session_alert_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1794,7 +1807,8 @@ def test_update_session_duration_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1834,7 +1848,8 @@ def test_update_session_invalidation_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1874,7 +1889,8 @@ def test_update_session_limit_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1914,7 +1930,8 @@ def test_update_user_limit_policy(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -1969,7 +1986,8 @@ def test_update_protocol(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -2010,7 +2028,8 @@ def test_update_service(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -2051,7 +2070,8 @@ def test_update_smtp(self, m): "services": [], "protocols": [], "blocks": [], - "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00" + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) diff --git a/test/services/test_tables_db.py b/test/services/test_tables_db.py index 7f2ecabf..888be71f 100644 --- a/test/services/test_tables_db.py +++ b/test/services/test_tables_db.py @@ -35,9 +35,7 @@ def test_create(self, m): "$createdAt": "2020-10-15T06:38:00.000+00:00", "$updatedAt": "2020-10-15T06:38:00.000+00:00", "enabled": True, - "type": "legacy", - "policies": [], - "archives": [] + "type": "legacy" } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -49,6 +47,27 @@ def test_create(self, m): self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() + def test_list_specifications(self, m): + data = { + "specifications": [], + "total": 9.0, + "pricing": { + "storageOverageRate": 0.125, + "bandwidthOverageRate": 0.08, + "replicaRate": 1, + "crossRegionReplicaRate": 1, + "pitrRate": 0.2 + } +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.tables_db.list_specifications( + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_list_transactions(self, m): data = { @@ -158,9 +177,7 @@ def test_get(self, m): "$createdAt": "2020-10-15T06:38:00.000+00:00", "$updatedAt": "2020-10-15T06:38:00.000+00:00", "enabled": True, - "type": "legacy", - "policies": [], - "archives": [] + "type": "legacy" } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -179,9 +196,7 @@ def test_update(self, m): "$createdAt": "2020-10-15T06:38:00.000+00:00", "$updatedAt": "2020-10-15T06:38:00.000+00:00", "enabled": True, - "type": "legacy", - "policies": [], - "archives": [] + "type": "legacy" } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -204,6 +219,106 @@ def test_delete(self, m): self.assertEqual(response, data) + @requests_mock.Mocker() + def test_create_failover(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "projectId": "5e5ea5c16897e", + "name": "My Production Database", + "api": "postgresql", + "engine": "postgresql", + "version": "16", + "specification": "s-2vcpu-2gb", + "backend": "edge", + "hostname": "db-myproject-mydb.fra.appwrite.center", + "connectionPort": 5432.0, + "connectionUser": "appwrite_user", + "connectionPassword": "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", + "connectionString": "postgresql:\/\/user:pass@db-myproject-mydb.fra.appwrite.center:5432\/postgres?sslmode=require", + "ssl": True, + "status": "ready", + "containerStatus": "active", + "lifecycleState": "active", + "idleTimeoutMinutes": 15.0, + "cpu": 2000.0, + "memory": 4096.0, + "storage": 100.0, + "storageClass": "ssd", + "storageMaxGb": 100.0, + "nodePool": "db-pool-4vcpu-8gb", + "replicas": 2.0, + "syncMode": "async", + "crossRegionReplicas": 1.0, + "networkMaxConnections": 500.0, + "networkIdleTimeoutSeconds": 900.0, + "networkIPAllowlist": [], + "backupEnabled": True, + "pitr": True, + "pitrRetentionDays": 14.0, + "storageAutoscaling": True, + "storageAutoscalingThresholdPercent": 85.0, + "storageAutoscalingMaxGb": 500.0, + "maintenanceWindowDay": "sun", + "maintenanceWindowHourUtc": 3.0, + "metricsEnabled": True, + "sqlApiEnabled": True, + "sqlApiAllowedStatements": [], + "sqlApiMaxRows": 10000.0, + "sqlApiMaxBytes": 10485760.0, + "sqlApiTimeoutSeconds": 30.0, + "error": "" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.tables_db.create_failover( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_replicas(self, m): + data = { + "replicas": 2.0, + "syncMode": "async", + "members": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.tables_db.get_replicas( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_status(self, m): + data = { + "health": "healthy", + "ready": True, + "engine": "postgresql", + "version": "17", + "uptime": 86400.0, + "connections": { + "current": 12.0, + "max": 100.0 + }, + "replicas": [], + "volumes": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.tables_db.get_status( + '', + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_list_tables(self, m): data = { From 6402b883973803040f71f50e2a4cdf1542f3e486 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 23 Jul 2026 16:17:34 +0530 Subject: [PATCH 4/8] chore: update Python SDK to 22.2.0 --- CHANGELOG.md | 9 + appwrite/client.py | 4 +- appwrite/enums/organization_key_scopes.py | 2 + appwrite/models/__init__.py | 8 + appwrite/models/app.py | 6 + appwrite/models/app_installation.py | 42 ++ appwrite/models/app_installation_list.py | 19 + appwrite/models/app_key.py | 39 ++ appwrite/models/app_key_list.py | 19 + appwrite/models/database.py | 6 + appwrite/models/project.py | 3 + appwrite/services/apps.py | 369 +++++++++++++++++- appwrite/services/organization.py | 213 ++++++++++ appwrite/services/project.py | 5 + appwrite/services/teams.py | 248 ++++++++++++ .../apps/create-installation-token.md | 19 + docs/examples/apps/create-key.md | 18 + docs/examples/apps/delete-key.md | 16 + docs/examples/apps/get-installation.md | 19 + docs/examples/apps/get-key.md | 19 + .../examples/apps/list-installation-scopes.md | 16 + docs/examples/apps/list-installations.md | 20 + docs/examples/apps/list-keys.md | 20 + docs/examples/apps/update.md | 4 +- .../organization/create-installation.md | 19 + .../organization/delete-installation.md | 15 + .../examples/organization/get-installation.md | 18 + .../organization/list-installations.md | 19 + .../organization/update-installation.md | 19 + .../project/update-o-auth-2-server.md | 1 + docs/examples/teams/create-installation.md | 20 + docs/examples/teams/delete-installation.md | 16 + docs/examples/teams/get-installation.md | 19 + docs/examples/teams/list-installations.md | 20 + docs/examples/teams/update-installation.md | 20 + pyproject.toml | 2 +- setup.py | 4 +- test/services/test_apps.py | 152 ++++++++ test/services/test_organization.py | 92 +++++ test/services/test_teams.py | 97 +++++ 40 files changed, 1669 insertions(+), 7 deletions(-) create mode 100644 appwrite/models/app_installation.py create mode 100644 appwrite/models/app_installation_list.py create mode 100644 appwrite/models/app_key.py create mode 100644 appwrite/models/app_key_list.py create mode 100644 docs/examples/apps/create-installation-token.md create mode 100644 docs/examples/apps/create-key.md create mode 100644 docs/examples/apps/delete-key.md create mode 100644 docs/examples/apps/get-installation.md create mode 100644 docs/examples/apps/get-key.md create mode 100644 docs/examples/apps/list-installation-scopes.md create mode 100644 docs/examples/apps/list-installations.md create mode 100644 docs/examples/apps/list-keys.md create mode 100644 docs/examples/organization/create-installation.md create mode 100644 docs/examples/organization/delete-installation.md create mode 100644 docs/examples/organization/get-installation.md create mode 100644 docs/examples/organization/list-installations.md create mode 100644 docs/examples/organization/update-installation.md create mode 100644 docs/examples/teams/create-installation.md create mode 100644 docs/examples/teams/delete-installation.md create mode 100644 docs/examples/teams/get-installation.md create mode 100644 docs/examples/teams/list-installations.md create mode 100644 docs/examples/teams/update-installation.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d0f3fdfd..4eba1752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## 22.2.0 + +* Added: `apps` installation and key management methods (`list_installations`, `get_installation`, `create_installation_token`, `list_installation_scopes`, `list_keys`, `create_key`, `get_key`, `delete_key`) +* Added: `installation_scopes` and `installation_redirect_url` parameters to `apps.update` +* Added: app installation management methods to `organization` and `teams` services +* Added: `installation_access_token_duration` parameter to `project.update_o_auth2_server` +* Added: `installationScopes` and `installationRedirectUrl` fields to `App` model +* Added: `organization.installations.read`/`organization.installations.write` key scopes + ## 22.1.0 * Updated: removed `new_specification` parameter from `backups.create_restoration` diff --git a/appwrite/client.py b/appwrite/client.py index 3588e3a7..6e1d40e7 100644 --- a/appwrite/client.py +++ b/appwrite/client.py @@ -17,11 +17,11 @@ def __init__(self): self._endpoint = 'https://cloud.appwrite.io/v1' self._global_headers = { 'content-type': '', - 'user-agent' : f'AppwritePythonSDK/22.1.0 ({platform.uname().system}; {platform.uname().version}; {platform.uname().machine})', + 'user-agent' : f'AppwritePythonSDK/22.2.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': '22.1.0', + 'x-sdk-version': '22.2.0', 'X-Appwrite-Response-Format' : '1.9.5', } self._config = {} diff --git a/appwrite/enums/organization_key_scopes.py b/appwrite/enums/organization_key_scopes.py index ab4e4526..bb2515df 100644 --- a/appwrite/enums/organization_key_scopes.py +++ b/appwrite/enums/organization_key_scopes.py @@ -7,6 +7,8 @@ class OrganizationKeyScopes(Enum): DEVKEYS_WRITE = "devKeys.write" ORGANIZATION_KEYS_READ = "organization.keys.read" ORGANIZATION_KEYS_WRITE = "organization.keys.write" + ORGANIZATION_INSTALLATIONS_READ = "organization.installations.read" + ORGANIZATION_INSTALLATIONS_WRITE = "organization.installations.write" ORGANIZATION_MEMBERSHIPS_READ = "organization.memberships.read" ORGANIZATION_MEMBERSHIPS_WRITE = "organization.memberships.write" ORGANIZATION_READ = "organization.read" diff --git a/appwrite/models/__init__.py b/appwrite/models/__init__.py index 07a73030..419db211 100644 --- a/appwrite/models/__init__.py +++ b/appwrite/models/__init__.py @@ -243,6 +243,8 @@ from .app_secret import AppSecret from .app_secret_plaintext import AppSecretPlaintext from .app_scope import AppScope +from .app_installation import AppInstallation +from .app_key import AppKey from .oauth2_authorize import Oauth2Authorize from .oauth2_approve import Oauth2Approve from .oauth2_reject import Oauth2Reject @@ -265,6 +267,8 @@ from .apps_list import AppsList from .app_secret_list import AppSecretList from .app_scope_list import AppScopeList +from .app_installation_list import AppInstallationList +from .app_key_list import AppKeyList __all__ = [ 'AppwriteModel', @@ -512,6 +516,8 @@ 'AppSecret', 'AppSecretPlaintext', 'AppScope', + 'AppInstallation', + 'AppKey', 'Oauth2Authorize', 'Oauth2Approve', 'Oauth2Reject', @@ -534,4 +540,6 @@ 'AppsList', 'AppSecretList', 'AppScopeList', + 'AppInstallationList', + 'AppKeyList', ] diff --git a/appwrite/models/app.py b/appwrite/models/app.py index f570a66c..76e93798 100644 --- a/appwrite/models/app.py +++ b/appwrite/models/app.py @@ -56,6 +56,10 @@ class App(AppwriteModel): ID of team that owns the application, if owned by team. Otherwise, user ID will be used. userid : str ID of user who owns the application, if owned by user. Otherwise, team ID will be used. + installationscopes : List[Any] + Scopes the application requests when installed on a team. Organization-level and project-level scopes only. + installationredirecturl : str + URL users are redirected to after creating or updating an installation of this application. Empty for no redirect. secrets : List[AppSecret] List of application secrets. """ @@ -82,4 +86,6 @@ class App(AppwriteModel): deviceflow: bool = Field(..., alias='deviceFlow') teamid: str = Field(..., alias='teamId') userid: str = Field(..., alias='userId') + installationscopes: List[Any] = Field(..., alias='installationScopes') + installationredirecturl: str = Field(..., alias='installationRedirectUrl') secrets: List[AppSecret] = Field(..., alias='secrets') diff --git a/appwrite/models/app_installation.py b/appwrite/models/app_installation.py new file mode 100644 index 00000000..3984389c --- /dev/null +++ b/appwrite/models/app_installation.py @@ -0,0 +1,42 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class AppInstallation(AppwriteModel): + """ + AppInstallation + + Attributes + ---------- + id : str + Installation ID. + createdat : str + Installation creation time in ISO 8601 format. + updatedat : str + Installation update time in ISO 8601 format. + appid : str + ID of the installed application. + teamid : str + ID of the team the application is installed on. + scopes : List[Any] + Scopes granted to the application. Snapshot of the application's installation scopes taken when the installation was created or last updated. + authorizationdetails : Dict[str, Any] + Authorization details granted to the application. Rich authorization request (RFC 9396) style entries; the Appwrite Console stores authorized project IDs here. + createdbyid : str + ID of the user who created the installation. + createdbyname : str + Name of the user who created the installation. + lastaccessedat : Optional[str] + Time an access token was last issued for the installation in ISO 8601 format. Null if never used. + """ + id: str = Field(..., alias='$id') + createdat: str = Field(..., alias='$createdAt') + updatedat: str = Field(..., alias='$updatedAt') + appid: str = Field(..., alias='appId') + teamid: str = Field(..., alias='teamId') + scopes: List[Any] = Field(..., alias='scopes') + authorizationdetails: Dict[str, Any] = Field(..., alias='authorizationDetails') + createdbyid: str = Field(..., alias='createdById') + createdbyname: str = Field(..., alias='createdByName') + lastaccessedat: Optional[str] = Field(default=None, alias='lastAccessedAt') diff --git a/appwrite/models/app_installation_list.py b/appwrite/models/app_installation_list.py new file mode 100644 index 00000000..0519077d --- /dev/null +++ b/appwrite/models/app_installation_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .app_installation import AppInstallation + +class AppInstallationList(AppwriteModel): + """ + App installations list + + Attributes + ---------- + total : float + Total number of installations that matched your query. + installations : List[AppInstallation] + List of installations. + """ + total: float = Field(..., alias='total') + installations: List[AppInstallation] = Field(..., alias='installations') diff --git a/appwrite/models/app_key.py b/appwrite/models/app_key.py new file mode 100644 index 00000000..88a319f2 --- /dev/null +++ b/appwrite/models/app_key.py @@ -0,0 +1,39 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class AppKey(AppwriteModel): + """ + AppKey + + Attributes + ---------- + id : str + App key ID. + createdat : str + App key creation time in ISO 8601 format. + updatedat : str + App key update time in ISO 8601 format. + appid : str + Application ID this app key belongs to. + secret : str + App key secret. + hint : str + Last few characters of the app key secret, used to help identify it. + createdbyid : str + ID of the user who created the app key. + createdbyname : str + Name of the user who created the app key. + lastaccessedat : Optional[str] + Time the app key was last used for authentication in ISO 8601 format. Null if never used. + """ + id: str = Field(..., alias='$id') + createdat: str = Field(..., alias='$createdAt') + updatedat: str = Field(..., alias='$updatedAt') + appid: str = Field(..., alias='appId') + secret: str = Field(..., alias='secret') + hint: str = Field(..., alias='hint') + createdbyid: str = Field(..., alias='createdById') + createdbyname: str = Field(..., alias='createdByName') + lastaccessedat: Optional[str] = Field(default=None, alias='lastAccessedAt') diff --git a/appwrite/models/app_key_list.py b/appwrite/models/app_key_list.py new file mode 100644 index 00000000..2d4b7ff4 --- /dev/null +++ b/appwrite/models/app_key_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .app_key import AppKey + +class AppKeyList(AppwriteModel): + """ + App keys list + + Attributes + ---------- + total : float + Total number of keys that matched your query. + keys : List[AppKey] + List of keys. + """ + total: float = Field(..., alias='total') + keys: List[AppKey] = Field(..., alias='keys') diff --git a/appwrite/models/database.py b/appwrite/models/database.py index f83198fd..c1852901 100644 --- a/appwrite/models/database.py +++ b/appwrite/models/database.py @@ -27,6 +27,10 @@ class Database(AppwriteModel): Database type. status : Optional[DatabaseStatus] Dedicated database lifecycle status. Null when the database has no valid dedicated backing. + engine : Optional[str] + Underlying engine of the dedicated backing: postgresql, mysql, mariadb, or mongodb. A managed product (tablesdb, documentsdb, vectorsdb) reports the engine it runs on, so its type and engine can differ. Null when the database has no dedicated backing. + specification : Optional[str] + Compute specification identifier of the dedicated backing, e.g. s-2vcpu-2gb. Null when the database has no dedicated backing. replicas : Optional[float] Number of secondary high availability replicas, excluding the primary. Null when backing configuration is unavailable. policies : Optional[List[BackupPolicy]] @@ -41,6 +45,8 @@ class Database(AppwriteModel): enabled: bool = Field(..., alias='enabled') type: DatabaseType = Field(..., alias='type') status: Optional[DatabaseStatus] = Field(default=None, alias='status') + engine: Optional[str] = Field(default=None, alias='engine') + specification: Optional[str] = Field(default=None, alias='specification') replicas: Optional[float] = Field(default=None, alias='replicas') policies: Optional[List[BackupPolicy]] = Field(default=None, alias='policies') archives: Optional[List[BackupArchive]] = Field(default=None, alias='archives') diff --git a/appwrite/models/project.py b/appwrite/models/project.py index 3dd2ca01..76822354 100644 --- a/appwrite/models/project.py +++ b/appwrite/models/project.py @@ -91,6 +91,8 @@ class Project(AppwriteModel): OAuth2 server access token duration in seconds for public clients (SPAs, mobile, native) oauth2serverpublicrefreshtokenduration : Optional[float] OAuth2 server refresh token duration in seconds for public clients (SPAs, mobile, native) + oauth2serverinstallationaccesstokenduration : Optional[float] + OAuth2 server access token duration in seconds for app installation access tokens oauth2serverconfidentialpkce : Optional[bool] When enabled, PKCE is required for confidential clients (server-side flows using client_secret). PKCE is always required for public clients regardless of this setting. oauth2serververificationurl : Optional[str] @@ -142,6 +144,7 @@ class Project(AppwriteModel): oauth2serverrefreshtokenduration: Optional[float] = Field(default=None, alias='oAuth2ServerRefreshTokenDuration') oauth2serverpublicaccesstokenduration: Optional[float] = Field(default=None, alias='oAuth2ServerPublicAccessTokenDuration') oauth2serverpublicrefreshtokenduration: Optional[float] = Field(default=None, alias='oAuth2ServerPublicRefreshTokenDuration') + oauth2serverinstallationaccesstokenduration: Optional[float] = Field(default=None, alias='oAuth2ServerInstallationAccessTokenDuration') oauth2serverconfidentialpkce: Optional[bool] = Field(default=None, alias='oAuth2ServerConfidentialPkce') oauth2serververificationurl: Optional[str] = Field(default=None, alias='oAuth2ServerVerificationUrl') oauth2serverusercodelength: Optional[float] = Field(default=None, alias='oAuth2ServerUserCodeLength') diff --git a/appwrite/services/apps.py b/appwrite/services/apps.py index 8b8e3285..d4a3f681 100644 --- a/appwrite/services/apps.py +++ b/appwrite/services/apps.py @@ -6,6 +6,11 @@ from ..models.apps_list import AppsList from ..models.app import App from ..models.app_scope_list import AppScopeList +from ..models.app_installation_list import AppInstallationList +from ..models.app_installation import AppInstallation +from ..models.oauth2_token import Oauth2Token +from ..models.app_key_list import AppKeyList +from ..models.app_key import AppKey from ..models.app_secret_list import AppSecretList from ..models.app_secret_plaintext import AppSecretPlaintext from ..models.app_secret import AppSecret @@ -191,6 +196,34 @@ def create( return self._parse_response(response, model=App) + def list_installation_scopes( + self + ) -> AppScopeList: + """ + List scopes an application can request when installed on a team. + + Returns + ------- + AppScopeList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/scopes/installations' + api_params = {} + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppScopeList) + + def list_o_auth2_scopes( self ) -> AppScopeList: @@ -277,7 +310,9 @@ def update( redirect_uris: Optional[List[str]] = None, post_logout_redirect_uris: Optional[List[str]] = None, type: Optional[str] = None, - device_flow: Optional[bool] = None + device_flow: Optional[bool] = None, + installation_scopes: Optional[List[str]] = None, + installation_redirect_url: Optional[str] = None ) -> App: """ Update an application by its unique ID. @@ -320,6 +355,10 @@ def update( OAuth2 client type. Use `public` for SPAs, mobile, and native apps that cannot keep a `client_secret` — PKCE is then required at the token endpoint. Use `confidential` for server-side clients that present a `client_secret`. Defaults to `confidential`. device_flow : Optional[bool] Allow this client to use the OAuth2 Device Authorization Grant (RFC 8628) for input-constrained devices such as TVs and CLIs. Defaults to false. + installation_scopes : Optional[List[str]] + Scopes the application requests when installed on a team. Organization-level and project-level scopes only; use the list scopes endpoint with `type=installation` to discover available values. Maximum of 100 scopes are allowed. + installation_redirect_url : Optional[str] + URL users are redirected to after creating or updating an installation of this application. Must be an https URL, an http loopback URL (localhost, 127.0.0.1, [::1]), or a private-use scheme URI, and must not contain a fragment. Leave empty for no redirect. Returns ------- @@ -375,6 +414,10 @@ def update( api_params['type'] = self._normalize_value(type) if device_flow is not None: api_params['deviceFlow'] = self._normalize_value(device_flow) + if installation_scopes is not None: + api_params['installationScopes'] = self._normalize_value(installation_scopes) + if installation_redirect_url is not None: + api_params['installationRedirectUrl'] = self._normalize_value(installation_redirect_url) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -425,6 +468,330 @@ def delete( return response + def list_installations( + self, + app_id: str, + queries: Optional[List[str]] = None, + total: Optional[bool] = None + ) -> AppInstallationList: + """ + List installations of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. + + Parameters + ---------- + app_id : str + Application unique ID. + queries : Optional[List[str]] + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + total : Optional[bool] + When set to false, the total count returned will be 0 and will not be calculated. + + Returns + ------- + AppInstallationList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/installations' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + + if queries is not None: + api_params['queries'] = self._normalize_value(queries) + if total is not None: + api_params['total'] = self._normalize_value(total) + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallationList) + + + def get_installation( + self, + app_id: str, + installation_id: str + ) -> AppInstallation: + """ + Get an installation of an application by its unique ID. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. + + Parameters + ---------- + app_id : str + Application unique ID. + installation_id : str + Installation unique ID. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/installations/{installationId}' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def create_installation_token( + self, + app_id: str, + installation_id: str + ) -> Oauth2Token: + """ + Create a token for an installation of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. The returned token carries the scopes and authorization details granted to the installation, and can be used as an `Authorization: Bearer` header everywhere OAuth2 access tokens are accepted. Multiple tokens can be active for the same installation at once; each token stays valid until it expires or the installation is updated or deleted. + + Parameters + ---------- + app_id : str + Application unique ID. + installation_id : str + Installation unique ID. + + Returns + ------- + Oauth2Token + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/installations/{installationId}/tokens' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Oauth2Token) + + + def list_keys( + self, + app_id: str, + queries: Optional[List[str]] = None, + total: Optional[bool] = None + ) -> AppKeyList: + """ + List app keys for an application. + + Parameters + ---------- + app_id : str + Application unique ID. + queries : Optional[List[str]] + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + total : Optional[bool] + When set to false, the total count returned will be 0 and will not be calculated. + + Returns + ------- + AppKeyList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/keys' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + + if queries is not None: + api_params['queries'] = self._normalize_value(queries) + if total is not None: + api_params['total'] = self._normalize_value(total) + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppKeyList) + + + def create_key( + self, + app_id: str + ) -> AppKey: + """ + Create a new app key for an application. App keys carry no scopes; send one in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header to list the application's installations and create installation access tokens. + + Parameters + ---------- + app_id : str + Application unique ID. + + Returns + ------- + AppKey + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/keys' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppKey) + + + def get_key( + self, + app_id: str, + key_id: str + ) -> AppKey: + """ + Get an app key by its unique ID. + + Parameters + ---------- + app_id : str + Application unique ID. + key_id : str + App key unique ID. + + Returns + ------- + AppKey + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/keys/{keyId}' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + if key_id is None: + raise AppwriteException('Missing required parameter: "key_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + api_path = api_path.replace('{keyId}', str(self._normalize_value(key_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppKey) + + + def delete_key( + self, + app_id: str, + key_id: str + ) -> Dict[str, Any]: + """ + Delete an app key by its unique ID. + + Parameters + ---------- + app_id : str + Application unique ID. + key_id : str + App key unique ID. + + Returns + ------- + Dict[str, Any] + API response as a dictionary + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/keys/{keyId}' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + if key_id is None: + raise AppwriteException('Missing required parameter: "key_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + api_path = api_path.replace('{keyId}', str(self._normalize_value(key_id))) + + + response = self.client.call('delete', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return response + + def update_labels( self, app_id: str, diff --git a/appwrite/services/organization.py b/appwrite/services/organization.py index e3f11e50..6ea8aa89 100644 --- a/appwrite/services/organization.py +++ b/appwrite/services/organization.py @@ -4,6 +4,8 @@ from ..exception import AppwriteException from appwrite.utils.deprecated import deprecated from ..models.organization import Organization as OrganizationModel +from ..models.app_installation_list import AppInstallationList +from ..models.app_installation import AppInstallation from ..models.key_list import KeyList from ..enums.organization_key_scopes import OrganizationKeyScopes from ..models.key import Key @@ -127,6 +129,217 @@ def delete( return response + def list_installations( + self, + queries: Optional[List[str]] = None, + total: Optional[bool] = None + ) -> AppInstallationList: + """ + List app installations on the organization. Any organization member can read installations. + + Parameters + ---------- + queries : Optional[List[str]] + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + total : Optional[bool] + When set to false, the total count returned will be 0 and will not be calculated. + + Returns + ------- + AppInstallationList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/installations' + api_params = {} + + if queries is not None: + api_params['queries'] = self._normalize_value(queries) + if total is not None: + api_params['total'] = self._normalize_value(total) + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallationList) + + + def create_installation( + self, + app_id: str, + authorization_details: Optional[str] = None + ) -> AppInstallation: + """ + Install an app on the organization. Only organization members with the owner role can install apps. The installation is granted the scopes the app currently requests. + + Parameters + ---------- + app_id : str + Application unique ID. + authorization_details : Optional[str] + Authorization details granted to the installation as a JSON array of objects, each with a `type` and app-defined fields. The Appwrite Console stores authorized project IDs here. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/installations' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + + api_params['appId'] = self._normalize_value(app_id) + if authorization_details is not None: + api_params['authorizationDetails'] = self._normalize_value(authorization_details) + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def get_installation( + self, + installation_id: str + ) -> AppInstallation: + """ + Get an app installation on the organization by its unique ID. Any organization member can read installations. + + Parameters + ---------- + installation_id : str + Installation unique ID. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/installations/{installationId}' + api_params = {} + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def update_installation( + self, + installation_id: str, + authorization_details: Optional[str] = None + ) -> AppInstallation: + """ + Update an app installation on the organization. Only organization members with the owner role can update installations. The installation's granted scopes are refreshed to the scopes the app currently requests; previously issued installation access tokens are revoked. + + Parameters + ---------- + installation_id : str + Installation unique ID. + authorization_details : Optional[str] + Authorization details granted to the installation as a JSON array of objects, each with a `type` and app-defined fields. Omit to keep the current value. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/installations/{installationId}' + api_params = {} + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + if authorization_details is not None: + api_params['authorizationDetails'] = self._normalize_value(authorization_details) + + response = self.client.call('put', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def delete_installation( + self, + installation_id: str + ) -> Dict[str, Any]: + """ + Uninstall an app from the organization by its installation ID. Only organization members with the owner role can remove installations. Previously issued installation access tokens are revoked. + + Parameters + ---------- + installation_id : str + Installation unique ID. + + Returns + ------- + Dict[str, Any] + API response as a dictionary + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/installations/{installationId}' + api_params = {} + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('delete', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return response + + def list_keys( self, queries: Optional[List[str]] = None, diff --git a/appwrite/services/project.py b/appwrite/services/project.py index 573abdae..38af962f 100644 --- a/appwrite/services/project.py +++ b/appwrite/services/project.py @@ -790,6 +790,7 @@ def update_o_auth2_server( refresh_token_duration: Optional[float] = None, public_access_token_duration: Optional[float] = None, public_refresh_token_duration: Optional[float] = None, + installation_access_token_duration: Optional[float] = None, confidential_pkce: Optional[bool] = None, verification_url: Optional[str] = None, user_code_length: Optional[float] = None, @@ -818,6 +819,8 @@ def update_o_auth2_server( Access token duration in seconds for public clients (SPAs, mobile, and native apps that cannot keep a client secret). Leave empty to use default 1 hour. public_refresh_token_duration : Optional[float] Refresh token duration in seconds for public clients (SPAs, mobile, and native apps that cannot keep a client secret). Leave empty to use default 30 days. + installation_access_token_duration : Optional[float] + Access token duration in seconds for app installation access tokens. Leave empty to use default 1 hour. confidential_pkce : Optional[bool] When enabled, PKCE is required for confidential clients (server-side flows using client_secret). PKCE is always required for public clients regardless of this setting. verification_url : Optional[str] @@ -865,6 +868,8 @@ def update_o_auth2_server( api_params['publicAccessTokenDuration'] = self._normalize_value(public_access_token_duration) if public_refresh_token_duration is not None: api_params['publicRefreshTokenDuration'] = self._normalize_value(public_refresh_token_duration) + if installation_access_token_duration is not None: + api_params['installationAccessTokenDuration'] = self._normalize_value(installation_access_token_duration) if confidential_pkce is not None: api_params['confidentialPkce'] = self._normalize_value(confidential_pkce) if verification_url is not None: diff --git a/appwrite/services/teams.py b/appwrite/services/teams.py index 6b8a6fc9..45594204 100644 --- a/appwrite/services/teams.py +++ b/appwrite/services/teams.py @@ -5,6 +5,8 @@ from appwrite.utils.deprecated import deprecated from ..models.team_list import TeamList from ..models.team import Team +from ..models.app_installation_list import AppInstallationList +from ..models.app_installation import AppInstallation from ..models.membership_list import MembershipList from ..models.membership import Membership from ..models.preferences import Preferences @@ -256,6 +258,252 @@ def delete( return response + def list_installations( + self, + team_id: str, + queries: Optional[List[str]] = None, + total: Optional[bool] = None + ) -> AppInstallationList: + """ + List app installations on a team. Any team member can read installations. + + Parameters + ---------- + team_id : str + Team ID. + queries : Optional[List[str]] + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + total : Optional[bool] + When set to false, the total count returned will be 0 and will not be calculated. + + Returns + ------- + AppInstallationList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/teams/{teamId}/installations' + api_params = {} + if team_id is None: + raise AppwriteException('Missing required parameter: "team_id"') + + api_path = api_path.replace('{teamId}', str(self._normalize_value(team_id))) + + if queries is not None: + api_params['queries'] = self._normalize_value(queries) + if total is not None: + api_params['total'] = self._normalize_value(total) + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallationList) + + + def create_installation( + self, + team_id: str, + app_id: str, + authorization_details: Optional[str] = None + ) -> AppInstallation: + """ + Install an app on a team. When authenticated as a user, only team members with the owner role can install apps. Requests using an API key or in admin mode can install apps on any team. The installation is granted the scopes the app currently requests. + + Parameters + ---------- + team_id : str + Team ID. + app_id : str + Application unique ID. + authorization_details : Optional[str] + Authorization details granted to the installation as a JSON array of objects, each with a `type` and app-defined fields. The Appwrite Console stores authorized project IDs here. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/teams/{teamId}/installations' + api_params = {} + if team_id is None: + raise AppwriteException('Missing required parameter: "team_id"') + + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + api_path = api_path.replace('{teamId}', str(self._normalize_value(team_id))) + + api_params['appId'] = self._normalize_value(app_id) + if authorization_details is not None: + api_params['authorizationDetails'] = self._normalize_value(authorization_details) + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def get_installation( + self, + team_id: str, + installation_id: str + ) -> AppInstallation: + """ + Get an app installation on a team by its unique ID. Any team member can read installations. + + Parameters + ---------- + team_id : str + Team ID. + installation_id : str + Installation unique ID. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/teams/{teamId}/installations/{installationId}' + api_params = {} + if team_id is None: + raise AppwriteException('Missing required parameter: "team_id"') + + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{teamId}', str(self._normalize_value(team_id))) + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def update_installation( + self, + team_id: str, + installation_id: str, + authorization_details: Optional[str] = None + ) -> AppInstallation: + """ + Update an app installation on a team. Only team members with the owner role can update installations. The installation's granted scopes are refreshed to the scopes the app currently requests; previously issued installation access tokens are revoked. + + Parameters + ---------- + team_id : str + Team ID. + installation_id : str + Installation unique ID. + authorization_details : Optional[str] + Authorization details granted to the installation as a JSON array of objects, each with a `type` and app-defined fields. Omit to keep the current value. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/teams/{teamId}/installations/{installationId}' + api_params = {} + if team_id is None: + raise AppwriteException('Missing required parameter: "team_id"') + + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{teamId}', str(self._normalize_value(team_id))) + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + if authorization_details is not None: + api_params['authorizationDetails'] = self._normalize_value(authorization_details) + + response = self.client.call('put', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def delete_installation( + self, + team_id: str, + installation_id: str + ) -> Dict[str, Any]: + """ + Uninstall an app from a team by its installation ID. Only team members with the owner role can remove installations. Previously issued installation access tokens are revoked. + + Parameters + ---------- + team_id : str + Team ID. + installation_id : str + Installation unique ID. + + Returns + ------- + Dict[str, Any] + API response as a dictionary + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/teams/{teamId}/installations/{installationId}' + api_params = {} + if team_id is None: + raise AppwriteException('Missing required parameter: "team_id"') + + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{teamId}', str(self._normalize_value(team_id))) + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('delete', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return response + + def list_memberships( self, team_id: str, diff --git a/docs/examples/apps/create-installation-token.md b/docs/examples/apps/create-installation-token.md new file mode 100644 index 00000000..62a62ca6 --- /dev/null +++ b/docs/examples/apps/create-installation-token.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import Oauth2Token + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +apps = Apps(client) + +result: Oauth2Token = apps.create_installation_token( + app_id = '', + installation_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/create-key.md b/docs/examples/apps/create-key.md new file mode 100644 index 00000000..2821ba65 --- /dev/null +++ b/docs/examples/apps/create-key.md @@ -0,0 +1,18 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppKey + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +apps = Apps(client) + +result: AppKey = apps.create_key( + app_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/delete-key.md b/docs/examples/apps/delete-key.md new file mode 100644 index 00000000..dca52e39 --- /dev/null +++ b/docs/examples/apps/delete-key.md @@ -0,0 +1,16 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +apps = Apps(client) + +result = apps.delete_key( + app_id = '', + key_id = '' +) +``` diff --git a/docs/examples/apps/get-installation.md b/docs/examples/apps/get-installation.md new file mode 100644 index 00000000..f6a52721 --- /dev/null +++ b/docs/examples/apps/get-installation.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +apps = Apps(client) + +result: AppInstallation = apps.get_installation( + app_id = '', + installation_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/get-key.md b/docs/examples/apps/get-key.md new file mode 100644 index 00000000..4df3d424 --- /dev/null +++ b/docs/examples/apps/get-key.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppKey + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +apps = Apps(client) + +result: AppKey = apps.get_key( + app_id = '', + key_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/list-installation-scopes.md b/docs/examples/apps/list-installation-scopes.md new file mode 100644 index 00000000..4705cf7b --- /dev/null +++ b/docs/examples/apps/list-installation-scopes.md @@ -0,0 +1,16 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppScopeList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +apps = Apps(client) + +result: AppScopeList = apps.list_installation_scopes() + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/list-installations.md b/docs/examples/apps/list-installations.md new file mode 100644 index 00000000..f5e292b2 --- /dev/null +++ b/docs/examples/apps/list-installations.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppInstallationList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +apps = Apps(client) + +result: AppInstallationList = apps.list_installations( + app_id = '', + queries = [], # optional + total = False # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/list-keys.md b/docs/examples/apps/list-keys.md new file mode 100644 index 00000000..7ed78543 --- /dev/null +++ b/docs/examples/apps/list-keys.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppKeyList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +apps = Apps(client) + +result: AppKeyList = apps.list_keys( + app_id = '', + queries = [], # optional + total = False # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/update.md b/docs/examples/apps/update.md index 49c85efb..c9006df6 100644 --- a/docs/examples/apps/update.md +++ b/docs/examples/apps/update.md @@ -28,7 +28,9 @@ result: App = apps.update( redirect_uris = [], # optional post_logout_redirect_uris = [], # optional type = 'public', # optional - device_flow = False # optional + device_flow = False, # optional + installation_scopes = [], # optional + installation_redirect_url = 'https://example.com' # optional ) print(result.model_dump()) diff --git a/docs/examples/organization/create-installation.md b/docs/examples/organization/create-installation.md new file mode 100644 index 00000000..c7af602a --- /dev/null +++ b/docs/examples/organization/create-installation.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.organization import Organization +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +organization = Organization(client) + +result: AppInstallation = organization.create_installation( + app_id = '', + authorization_details = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/organization/delete-installation.md b/docs/examples/organization/delete-installation.md new file mode 100644 index 00000000..82790282 --- /dev/null +++ b/docs/examples/organization/delete-installation.md @@ -0,0 +1,15 @@ +```python +from appwrite.client import Client +from appwrite.services.organization import Organization + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +organization = Organization(client) + +result = organization.delete_installation( + installation_id = '' +) +``` diff --git a/docs/examples/organization/get-installation.md b/docs/examples/organization/get-installation.md new file mode 100644 index 00000000..0bd78a30 --- /dev/null +++ b/docs/examples/organization/get-installation.md @@ -0,0 +1,18 @@ +```python +from appwrite.client import Client +from appwrite.services.organization import Organization +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +organization = Organization(client) + +result: AppInstallation = organization.get_installation( + installation_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/organization/list-installations.md b/docs/examples/organization/list-installations.md new file mode 100644 index 00000000..c2ebe4e8 --- /dev/null +++ b/docs/examples/organization/list-installations.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.organization import Organization +from appwrite.models import AppInstallationList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +organization = Organization(client) + +result: AppInstallationList = organization.list_installations( + queries = [], # optional + total = False # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/organization/update-installation.md b/docs/examples/organization/update-installation.md new file mode 100644 index 00000000..ab8301b2 --- /dev/null +++ b/docs/examples/organization/update-installation.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.organization import Organization +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +organization = Organization(client) + +result: AppInstallation = organization.update_installation( + installation_id = '', + authorization_details = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/project/update-o-auth-2-server.md b/docs/examples/project/update-o-auth-2-server.md index 6be24c54..bce0876e 100644 --- a/docs/examples/project/update-o-auth-2-server.md +++ b/docs/examples/project/update-o-auth-2-server.md @@ -19,6 +19,7 @@ result: ProjectModel = project.update_o_auth2_server( refresh_token_duration = 60, # optional public_access_token_duration = 60, # optional public_refresh_token_duration = 60, # optional + installation_access_token_duration = 60, # optional confidential_pkce = False, # optional verification_url = 'https://example.com', # optional user_code_length = 6, # optional diff --git a/docs/examples/teams/create-installation.md b/docs/examples/teams/create-installation.md new file mode 100644 index 00000000..185e379f --- /dev/null +++ b/docs/examples/teams/create-installation.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.teams import Teams +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +teams = Teams(client) + +result: AppInstallation = teams.create_installation( + team_id = '', + app_id = '', + authorization_details = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/teams/delete-installation.md b/docs/examples/teams/delete-installation.md new file mode 100644 index 00000000..912195d3 --- /dev/null +++ b/docs/examples/teams/delete-installation.md @@ -0,0 +1,16 @@ +```python +from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +teams = Teams(client) + +result = teams.delete_installation( + team_id = '', + installation_id = '' +) +``` diff --git a/docs/examples/teams/get-installation.md b/docs/examples/teams/get-installation.md new file mode 100644 index 00000000..06ad5f01 --- /dev/null +++ b/docs/examples/teams/get-installation.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.teams import Teams +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +teams = Teams(client) + +result: AppInstallation = teams.get_installation( + team_id = '', + installation_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/teams/list-installations.md b/docs/examples/teams/list-installations.md new file mode 100644 index 00000000..fbc9485b --- /dev/null +++ b/docs/examples/teams/list-installations.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.teams import Teams +from appwrite.models import AppInstallationList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +teams = Teams(client) + +result: AppInstallationList = teams.list_installations( + team_id = '', + queries = [], # optional + total = False # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/teams/update-installation.md b/docs/examples/teams/update-installation.md new file mode 100644 index 00000000..a5df1213 --- /dev/null +++ b/docs/examples/teams/update-installation.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.teams import Teams +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +teams = Teams(client) + +result: AppInstallation = teams.update_installation( + team_id = '', + installation_id = '', + authorization_details = '' # optional +) + +print(result.model_dump()) +``` diff --git a/pyproject.toml b/pyproject.toml index 0d439733..b71d5337 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "appwrite" -version = "22.1.0" +version = "22.2.0" description = "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API" readme = "README.md" requires-python = ">=3.9" diff --git a/setup.py b/setup.py index 1baebf1e..3d51ebff 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name = 'appwrite', packages = setuptools.find_packages(), - version = '22.1.0', + version = '22.2.0', license='BSD-3-Clause', description = 'Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API', long_description = long_description, @@ -18,7 +18,7 @@ maintainer = 'Appwrite Team', maintainer_email = 'team@appwrite.io', url = 'https://appwrite.io/support', - download_url='https://github.com/appwrite/sdk-for-python/archive/22.1.0.tar.gz', + download_url='https://github.com/appwrite/sdk-for-python/archive/22.2.0.tar.gz', install_requires=[ 'requests', 'pydantic>=2,<3', diff --git a/test/services/test_apps.py b/test/services/test_apps.py index e457b572..e923f2e5 100644 --- a/test/services/test_apps.py +++ b/test/services/test_apps.py @@ -53,6 +53,8 @@ def test_create(self, m): "deviceFlow": True, "teamId": "5e5ea5c16897e", "userId": "5e5ea5c16897e", + "installationScopes": [], + "installationRedirectUrl": "https:\/\/example.com\/setup", "secrets": [] } headers = {'Content-Type': 'application/json'} @@ -66,6 +68,20 @@ def test_create(self, m): self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() + def test_list_installation_scopes(self, m): + data = { + "total": 5.0, + "scopes": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.list_installation_scopes( + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_list_o_auth2_scopes(self, m): data = { @@ -106,6 +122,8 @@ def test_get(self, m): "deviceFlow": True, "teamId": "5e5ea5c16897e", "userId": "5e5ea5c16897e", + "installationScopes": [], + "installationRedirectUrl": "https:\/\/example.com\/setup", "secrets": [] } headers = {'Content-Type': 'application/json'} @@ -143,6 +161,8 @@ def test_update(self, m): "deviceFlow": True, "teamId": "5e5ea5c16897e", "userId": "5e5ea5c16897e", + "installationScopes": [], + "installationRedirectUrl": "https:\/\/example.com\/setup", "secrets": [] } headers = {'Content-Type': 'application/json'} @@ -167,6 +187,134 @@ def test_delete(self, m): self.assertEqual(response, data) + @requests_mock.Mocker() + def test_list_installations(self, m): + data = { + "total": 5.0, + "installations": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.list_installations( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.get_installation( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_create_installation_token(self, m): + data = { + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...", + "token_type": "Bearer", + "expires_in": 3600.0, + "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...", + "scope": "openid email profile" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.create_installation_token( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_list_keys(self, m): + data = { + "total": 5.0, + "keys": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.list_keys( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_create_key(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "secret": "5f3c8d2a1b9e4f7a6c8b2d1e9f4a7b3c5d8e1f2a9b4c7d6e3f5a8b1c4d7e2f9a", + "hint": "f5c6c7", + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.create_key( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_key(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "secret": "5f3c8d2a1b9e4f7a6c8b2d1e9f4a7b3c5d8e1f2a9b4c7d6e3f5a8b1c4d7e2f9a", + "hint": "f5c6c7", + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.get_key( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_delete_key(self, m): + data = '' + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.delete_key( + '', + '', + ) + + self.assertEqual(response, data) + @requests_mock.Mocker() def test_update_labels(self, m): data = { @@ -193,6 +341,8 @@ def test_update_labels(self, m): "deviceFlow": True, "teamId": "5e5ea5c16897e", "userId": "5e5ea5c16897e", + "installationScopes": [], + "installationRedirectUrl": "https:\/\/example.com\/setup", "secrets": [] } headers = {'Content-Type': 'application/json'} @@ -302,6 +452,8 @@ def test_update_team(self, m): "deviceFlow": True, "teamId": "5e5ea5c16897e", "userId": "5e5ea5c16897e", + "installationScopes": [], + "installationRedirectUrl": "https:\/\/example.com\/setup", "secrets": [] } headers = {'Content-Type': 'application/json'} diff --git a/test/services/test_organization.py b/test/services/test_organization.py index 399dd374..544d647d 100644 --- a/test/services/test_organization.py +++ b/test/services/test_organization.py @@ -477,6 +477,98 @@ def test_delete(self, m): self.assertEqual(response, data) + @requests_mock.Mocker() + def test_list_installations(self, m): + data = { + "total": 5.0, + "installations": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.list_installations( + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_create_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.create_installation( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.get_installation( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_update_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.update_installation( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_delete_installation(self, m): + data = '' + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.delete_installation( + '', + ) + + self.assertEqual(response, data) + @requests_mock.Mocker() def test_list_keys(self, m): data = { diff --git a/test/services/test_teams.py b/test/services/test_teams.py index 84688663..684d00f8 100644 --- a/test/services/test_teams.py +++ b/test/services/test_teams.py @@ -98,6 +98,103 @@ def test_delete(self, m): self.assertEqual(response, data) + @requests_mock.Mocker() + def test_list_installations(self, m): + data = { + "total": 5.0, + "installations": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.teams.list_installations( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_create_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.teams.create_installation( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.teams.get_installation( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_update_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.teams.update_installation( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_delete_installation(self, m): + data = '' + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.teams.delete_installation( + '', + '', + ) + + self.assertEqual(response, data) + @requests_mock.Mocker() def test_list_memberships(self, m): data = { From 253f92583fa46b13199b11fc92a5b7334b6f2216 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 23 Jul 2026 17:01:53 +0530 Subject: [PATCH 5/8] chore: update Python SDK to 22.2.0 --- .github/workflows/publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f0e30cb5..53679c3b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,10 +9,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.9' From a29b3154708246695c691952362cb1691762cecb Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 14 Aug 2026 13:41:48 +0530 Subject: [PATCH 6/8] chore: update Python SDK to 23.0.0 --- .github/workflows/publish.yml | 22 +- CHANGELOG.md | 18 + README.md | 2 +- appwrite/client.py | 13 +- appwrite/encoders/value_class_encoder.py | 8 + appwrite/enums/authentication_factor.py | 1 + appwrite/enums/build_runtime.py | 1 + appwrite/enums/embedding_model.py | 7 + appwrite/enums/invalidation_type.py | 6 + appwrite/enums/project_key_scopes.py | 2 + appwrite/enums/project_policy_id.py | 1 + appwrite/enums/runtime.py | 1 + appwrite/models/__init__.py | 18 + appwrite/models/billing_plan.py | 16 +- appwrite/models/billing_plan_addon.py | 8 +- appwrite/models/billing_plan_addon_details.py | 4 +- appwrite/models/database.py | 2 +- appwrite/models/database_migration.py | 60 ++++ appwrite/models/database_migration_list.py | 19 ++ appwrite/models/database_status.py | 24 +- .../models/database_status_connections.py | 2 +- appwrite/models/database_status_replica.py | 4 +- appwrite/models/dedicated_database.py | 9 +- appwrite/models/dedicated_database_member.py | 10 +- .../models/dedicated_database_operation.py | 45 +++ .../dedicated_database_operation_list.py | 19 ++ .../models/dedicated_database_replicas.py | 17 +- ...edicated_database_specification_pricing.py | 3 - appwrite/models/document.py | 81 +++-- appwrite/models/document_list.py | 2 +- appwrite/models/embedding.py | 24 ++ appwrite/models/embedding_list.py | 19 ++ appwrite/models/file.py | 6 + appwrite/models/mfa_challenge_secret.py | 27 ++ appwrite/models/mfa_factors.py | 3 + appwrite/models/organization.py | 50 +-- appwrite/models/policy_list.py | 5 +- appwrite/models/policy_mfa_factors.py | 27 ++ appwrite/models/preferences.py | 59 ++-- appwrite/models/project.py | 3 + appwrite/models/proxy_invalidation.py | 24 ++ appwrite/models/row.py | 81 +++-- appwrite/models/row_list.py | 2 +- appwrite/models/team_list.py | 2 +- appwrite/models/usage_billing_plan.py | 12 +- appwrite/models/user_list.py | 2 +- appwrite/service.py | 3 +- appwrite/services/account.py | 41 +-- appwrite/services/activities.py | 4 +- appwrite/services/apps.py | 57 +++- appwrite/services/backups.py | 6 +- appwrite/services/databases.py | 8 +- appwrite/services/embeddings.py | 58 ++++ appwrite/services/functions.py | 2 +- appwrite/services/project.py | 148 ++++---- appwrite/services/proxy.py | 56 ++++ appwrite/services/sites.py | 2 +- appwrite/services/storage.py | 7 +- appwrite/services/tables_db.py | 317 +++++++++++++++++- appwrite/services/users.py | 47 +++ docs/examples/activities/list-events.md | 2 +- .../apps/create-installation-token.md | 2 +- .../delete-installation.md} | 12 +- docs/examples/apps/get-installation.md | 2 +- docs/examples/apps/list-installations.md | 2 +- .../embeddings/create-text-embeddings.md | 20 ++ docs/examples/project/get-policy.md | 3 +- ...te-key.md => update-mfa-factors-policy.md} | 13 +- .../project/update-o-auth-2-server.md | 3 +- .../project/update-session-duration-policy.md | 2 +- .../project/update-user-limit-policy.md | 2 +- docs/examples/proxy/create-invalidation.md | 21 ++ docs/examples/storage/create-file.md | 3 +- docs/examples/tablesdb/create-migration.md | 20 ++ docs/examples/tablesdb/create.md | 3 +- docs/examples/tablesdb/cutover-migration.md | 19 ++ docs/examples/tablesdb/delete-migration.md | 16 + docs/examples/tablesdb/get-migration.md | 19 ++ docs/examples/tablesdb/list-migrations.md | 18 + docs/examples/tablesdb/list-operations.md | 21 ++ docs/examples/tablesdb/update.md | 4 +- docs/examples/users/get-mfa-challenge.md | 19 ++ pyproject.toml | 2 +- setup.py | 4 +- test/services/test_account.py | 20 +- test/services/test_apps.py | 13 + test/services/test_embeddings.py | 30 ++ test/services/test_organization.py | 126 +------ test/services/test_project.py | 64 ++-- test/services/test_proxy.py | 18 + test/services/test_storage.py | 6 + test/services/test_tables_db.py | 142 +++++++- test/services/test_teams.py | 2 - test/services/test_users.py | 27 +- 94 files changed, 1724 insertions(+), 483 deletions(-) create mode 100644 appwrite/enums/embedding_model.py create mode 100644 appwrite/enums/invalidation_type.py create mode 100644 appwrite/models/database_migration.py create mode 100644 appwrite/models/database_migration_list.py create mode 100644 appwrite/models/dedicated_database_operation.py create mode 100644 appwrite/models/dedicated_database_operation_list.py create mode 100644 appwrite/models/embedding.py create mode 100644 appwrite/models/embedding_list.py create mode 100644 appwrite/models/mfa_challenge_secret.py create mode 100644 appwrite/models/policy_mfa_factors.py create mode 100644 appwrite/models/proxy_invalidation.py create mode 100644 appwrite/services/embeddings.py rename docs/examples/{account/create-jwt.md => apps/delete-installation.md} (58%) create mode 100644 docs/examples/embeddings/create-text-embeddings.md rename docs/examples/project/{create-key.md => update-mfa-factors-policy.md} (58%) create mode 100644 docs/examples/proxy/create-invalidation.md create mode 100644 docs/examples/tablesdb/create-migration.md create mode 100644 docs/examples/tablesdb/cutover-migration.md create mode 100644 docs/examples/tablesdb/delete-migration.md create mode 100644 docs/examples/tablesdb/get-migration.md create mode 100644 docs/examples/tablesdb/list-migrations.md create mode 100644 docs/examples/tablesdb/list-operations.md create mode 100644 docs/examples/users/get-mfa-challenge.md create mode 100644 test/services/test_embeddings.py diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 53679c3b..dfbf4972 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,14 +2,28 @@ name: Publish to PyPI on: release: types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to publish, e.g. 1.2.3' + required: true jobs: publish: name: Release build and publish runs-on: ubuntu-latest + permissions: + # Mints the short-lived OIDC token PyPI exchanges for an upload token, so + # no long-lived API token has to be stored on the repository. + id-token: write steps: - name: Check out code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # A release build must come from the tag, never from a branch that may + # have moved on. On a release event github.ref is already the tag; on a + # manual retry the tag has to be named explicitly. + ref: ${{ inputs.tag || github.ref }} - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 @@ -22,10 +36,4 @@ jobs: python setup.py sdist bdist_wheel - name: Publish package - run: | - python -m pip install twine - python -m twine upload -r pypi dist/* - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - TWINE_NON_INTERACTIVE: true + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eba1752..52fb6a98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## 23.0.0 + +* Breaking: removed `account.createJWT`; use `users.createJWT` instead. A leaked JWT could mint further JWTs, letting a credential outlive its own expiry — a session cannot duplicate itself to live forever either +* Breaking: removed `project.createKey`. A leaked key could mint further hidden keys, making a compromise far harder to contain and revoke +* Breaking: `activities.listEvents` `queries` now takes an array instead of a string +* Added: `embeddings` service with `createTextEmbeddings`, plus the `EmbeddingModel` enum +* Added: TablesDB migration methods `listMigrations`, `createMigration`, `getMigration`, `deleteMigration`, `cutoverMigration`, and `listOperations` +* Added: `proxy.createInvalidation` for purging cached edge responses, plus the `InvalidationType` enum +* Added: `apps.deleteInstallation` +* Added: `users.getMFAChallenge` and the `MfaChallengeSecret` model +* Added: `project.updateMFAFactorsPolicy` and the `PolicyMfaFactors` model +* Added: `client.setOrganization` for organization-scoped requests +* Added: `folder` parameter to `storage.createFile` +* Added: `syncMode` parameter to `tablesDB.create` and `tablesDB.update`, and `specification` to `tablesDB.update` +* Added: `installationScopes` parameter to `project.updateOAuth2Server` +* Added: `custom` authentication factor, `node-26` runtime, `mfa-factors` project policy, and the `embeddings.write` and `proxy.invalidations.write` key scopes +* Updated: response format to `1.9.6` + ## 22.2.0 * Added: `apps` installation and key management methods (`list_installations`, `get_installation`, `create_installation_token`, `list_installation_scopes`, `list_keys`, `create_key`, `get_key`, `delete_key`) diff --git a/README.md b/README.md index 60a89373..926d11a4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # 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.5-blue.svg?style=flat-square) +![Version](https://img.shields.io/badge/api%20version-1.9.6-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) diff --git a/appwrite/client.py b/appwrite/client.py index 6e1d40e7..67ba5631 100644 --- a/appwrite/client.py +++ b/appwrite/client.py @@ -17,12 +17,12 @@ def __init__(self): self._endpoint = 'https://cloud.appwrite.io/v1' self._global_headers = { 'content-type': '', - 'user-agent' : f'AppwritePythonSDK/22.2.0 ({platform.uname().system}; {platform.uname().version}; {platform.uname().machine})', + 'user-agent' : f'AppwritePythonSDK/23.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': '22.2.0', - 'X-Appwrite-Response-Format' : '1.9.5', + 'x-sdk-version': '23.0.0', + 'X-Appwrite-Response-Format' : '1.9.6', } self._config = {} @@ -60,6 +60,13 @@ def set_key(self, value): self._config['key'] = value return self + def set_organization(self, value): + """Your organization ID""" + + self._global_headers['x-appwrite-organization'] = value + self._config['organization'] = value + return self + def set_jwt(self, value): """Your secret JSON Web Token""" diff --git a/appwrite/encoders/value_class_encoder.py b/appwrite/encoders/value_class_encoder.py index b725fb9b..ab46a105 100644 --- a/appwrite/encoders/value_class_encoder.py +++ b/appwrite/encoders/value_class_encoder.py @@ -15,6 +15,7 @@ from ..enums.relation_mutate import RelationMutate from ..enums.databases_index_type import DatabasesIndexType from ..enums.order_by import OrderBy +from ..enums.embedding_model import EmbeddingModel from ..enums.runtime import Runtime from ..enums.project_key_scopes import ProjectKeyScopes from ..enums.template_reference_type import TemplateReferenceType @@ -35,6 +36,7 @@ from ..enums.project_smtp_secure import ProjectSMTPSecure from ..enums.project_email_template_id import ProjectEmailTemplateId from ..enums.project_email_template_locale import ProjectEmailTemplateLocale +from ..enums.invalidation_type import InvalidationType from ..enums.status_code import StatusCode from ..enums.proxy_resource_type import ProxyResourceType from ..enums.framework import Framework @@ -111,6 +113,9 @@ def default(self, o): if isinstance(o, OrderBy): return o.value + if isinstance(o, EmbeddingModel): + return o.value + if isinstance(o, Runtime): return o.value @@ -171,6 +176,9 @@ def default(self, o): if isinstance(o, ProjectEmailTemplateLocale): return o.value + if isinstance(o, InvalidationType): + return o.value + if isinstance(o, StatusCode): return o.value diff --git a/appwrite/enums/authentication_factor.py b/appwrite/enums/authentication_factor.py index a5b8cf2a..9e1f1add 100644 --- a/appwrite/enums/authentication_factor.py +++ b/appwrite/enums/authentication_factor.py @@ -5,3 +5,4 @@ class AuthenticationFactor(Enum): PHONE = "phone" TOTP = "totp" RECOVERYCODE = "recoverycode" + CUSTOM = "custom" diff --git a/appwrite/enums/build_runtime.py b/appwrite/enums/build_runtime.py index 7b1c6dc1..8472bd82 100644 --- a/appwrite/enums/build_runtime.py +++ b/appwrite/enums/build_runtime.py @@ -11,6 +11,7 @@ class BuildRuntime(Enum): NODE_23 = "node-23" NODE_24 = "node-24" NODE_25 = "node-25" + NODE_26 = "node-26" PHP_8_0 = "php-8.0" PHP_8_1 = "php-8.1" PHP_8_2 = "php-8.2" diff --git a/appwrite/enums/embedding_model.py b/appwrite/enums/embedding_model.py new file mode 100644 index 00000000..5bbf1cf9 --- /dev/null +++ b/appwrite/enums/embedding_model.py @@ -0,0 +1,7 @@ +from enum import Enum + +class EmbeddingModel(Enum): + NOMIC_EMBED_TEXT = "nomic-embed-text" + EMBEDDING_GEMMA = "embedding-gemma" + ALL_MINILM = "all-minilm" + BGE_SMALL = "bge-small" diff --git a/appwrite/enums/invalidation_type.py b/appwrite/enums/invalidation_type.py new file mode 100644 index 00000000..1a8f7707 --- /dev/null +++ b/appwrite/enums/invalidation_type.py @@ -0,0 +1,6 @@ +from enum import Enum + +class InvalidationType(Enum): + TAG = "tag" + PATH = "path" + ALL = "all" diff --git a/appwrite/enums/project_key_scopes.py b/appwrite/enums/project_key_scopes.py index e29a2454..d96ff939 100644 --- a/appwrite/enums/project_key_scopes.py +++ b/appwrite/enums/project_key_scopes.py @@ -35,6 +35,7 @@ class ProjectKeyScopes(Enum): INDEXES_WRITE = "indexes.write" ROWS_READ = "rows.read" ROWS_WRITE = "rows.write" + EMBEDDINGS_WRITE = "embeddings.write" COLLECTIONS_READ = "collections.read" COLLECTIONS_WRITE = "collections.write" ATTRIBUTES_READ = "attributes.read" @@ -99,6 +100,7 @@ class ProjectKeyScopes(Enum): WAFRULES_READ = "wafRules.read" WAFRULES_WRITE = "wafRules.write" EVENTS_READ = "events.read" + PROXY_INVALIDATIONS_WRITE = "proxy.invalidations.write" APPS_READ = "apps.read" APPS_WRITE = "apps.write" OAUTH2_READ = "oauth2.read" diff --git a/appwrite/enums/project_policy_id.py b/appwrite/enums/project_policy_id.py index 56ca022b..02b97a3e 100644 --- a/appwrite/enums/project_policy_id.py +++ b/appwrite/enums/project_policy_id.py @@ -11,6 +11,7 @@ class ProjectPolicyId(Enum): SESSION_LIMIT = "session-limit" USER_LIMIT = "user-limit" MEMBERSHIP_PRIVACY = "membership-privacy" + MFA_FACTORS = "mfa-factors" DENY_ALIASED_EMAIL = "deny-aliased-email" DENY_DISPOSABLE_EMAIL = "deny-disposable-email" DENY_FREE_EMAIL = "deny-free-email" diff --git a/appwrite/enums/runtime.py b/appwrite/enums/runtime.py index 74e3a279..0bc261ca 100644 --- a/appwrite/enums/runtime.py +++ b/appwrite/enums/runtime.py @@ -11,6 +11,7 @@ class Runtime(Enum): NODE_23 = "node-23" NODE_24 = "node-24" NODE_25 = "node-25" + NODE_26 = "node-26" PHP_8_0 = "php-8.0" PHP_8_1 = "php-8.1" PHP_8_2 = "php-8.2" diff --git a/appwrite/models/__init__.py b/appwrite/models/__init__.py index 419db211..9b7d0ced 100644 --- a/appwrite/models/__init__.py +++ b/appwrite/models/__init__.py @@ -43,9 +43,11 @@ from .target_list import TargetList from .transaction_list import TransactionList from .specification_list import SpecificationList +from .embedding_list import EmbeddingList from .insight_list import InsightList from .report_list import ReportList from .database import Database +from .embedding import Embedding from .collection import Collection from .attribute_list import AttributeList from .attribute_string import AttributeString @@ -180,6 +182,7 @@ from .policy_session_limit import PolicySessionLimit from .policy_user_limit import PolicyUserLimit from .policy_membership_privacy import PolicyMembershipPrivacy +from .policy_mfa_factors import PolicyMfaFactors from .platform_web import PlatformWeb from .platform_apple import PlatformApple from .platform_android import PlatformAndroid @@ -197,6 +200,7 @@ from .proxy_rule import ProxyRule from .email_template import EmailTemplate from .mfa_challenge import MfaChallenge +from .mfa_challenge_secret import MfaChallengeSecret from .mfa_recovery_codes import MfaRecoveryCodes from .mfa_type import MfaType from .mfa_factors import MfaFactors @@ -220,10 +224,14 @@ from .billing_plan_dedicated_database_limits import BillingPlanDedicatedDatabaseLimits from .billing_plan_supported_addons import BillingPlanSupportedAddons from .block import Block +from .database_migration import DatabaseMigration from .dedicated_database import DedicatedDatabase from .database_status import DatabaseStatus from .dedicated_database_member import DedicatedDatabaseMember +from .dedicated_database_operation import DedicatedDatabaseOperation +from .dedicated_database_operation_list import DedicatedDatabaseOperationList from .dedicated_database_replicas import DedicatedDatabaseReplicas +from .proxy_invalidation import ProxyInvalidation from .organization import Organization from .backup_policy import BackupPolicy from .policy_deny_aliased_email import PolicyDenyAliasedEmail @@ -264,6 +272,7 @@ from .backup_archive_list import BackupArchiveList from .backup_policy_list import BackupPolicyList from .backup_restoration_list import BackupRestorationList +from .database_migration_list import DatabaseMigrationList from .apps_list import AppsList from .app_secret_list import AppSecretList from .app_scope_list import AppScopeList @@ -316,9 +325,11 @@ 'TargetList', 'TransactionList', 'SpecificationList', + 'EmbeddingList', 'InsightList', 'ReportList', 'Database', + 'Embedding', 'Collection', 'AttributeList', 'AttributeString', @@ -453,6 +464,7 @@ 'PolicySessionLimit', 'PolicyUserLimit', 'PolicyMembershipPrivacy', + 'PolicyMfaFactors', 'PlatformWeb', 'PlatformApple', 'PlatformAndroid', @@ -470,6 +482,7 @@ 'ProxyRule', 'EmailTemplate', 'MfaChallenge', + 'MfaChallengeSecret', 'MfaRecoveryCodes', 'MfaType', 'MfaFactors', @@ -493,10 +506,14 @@ 'BillingPlanDedicatedDatabaseLimits', 'BillingPlanSupportedAddons', 'Block', + 'DatabaseMigration', 'DedicatedDatabase', 'DatabaseStatus', 'DedicatedDatabaseMember', + 'DedicatedDatabaseOperation', + 'DedicatedDatabaseOperationList', 'DedicatedDatabaseReplicas', + 'ProxyInvalidation', 'Organization', 'BackupPolicy', 'PolicyDenyAliasedEmail', @@ -537,6 +554,7 @@ 'BackupArchiveList', 'BackupPolicyList', 'BackupRestorationList', + 'DatabaseMigrationList', 'AppsList', 'AppSecretList', 'AppScopeList', diff --git a/appwrite/models/billing_plan.py b/appwrite/models/billing_plan.py index c37db9d3..dfe4d167 100644 --- a/appwrite/models/billing_plan.py +++ b/appwrite/models/billing_plan.py @@ -36,7 +36,7 @@ class BillingPlan(AppwriteModel): Image Transformations screenshotsgenerated : float Screenshots generated - members : float + members : Optional[float] Members webhooks : float Webhooks @@ -84,7 +84,7 @@ class BillingPlan(AppwriteModel): SMS authentications per month domains : float Custom domains - activitylogs : float + activitylogs : Optional[float] Activity log days usagelogs : float Usage history days @@ -132,13 +132,13 @@ class BillingPlan(AppwriteModel): Does plan support restricting sign-ups to corporate email addresses only. supportsprojectspecificroles : bool Does plan support project-specific member roles. - backupsenabled : bool + backupsenabled : Optional[bool] Does plan support backup policies. usageperproject : bool Whether usage addons are calculated per project. supportedaddons : BillingPlanSupportedAddons Supported addons for this plan - backuppolicies : float + backuppolicies : Optional[float] How many policies does plan support deploymentsize : float Maximum function and site deployment size in MB @@ -165,7 +165,7 @@ class BillingPlan(AppwriteModel): storage: float = Field(..., alias='storage') imagetransformations: float = Field(..., alias='imageTransformations') screenshotsgenerated: float = Field(..., alias='screenshotsGenerated') - members: float = Field(..., alias='members') + members: Optional[float] = Field(default=None, alias='members') webhooks: float = Field(..., alias='webhooks') wafrules: float = Field(..., alias='wafRules') projects: float = Field(..., alias='projects') @@ -189,7 +189,7 @@ class BillingPlan(AppwriteModel): topics: float = Field(..., alias='topics') authphone: float = Field(..., alias='authPhone') domains: float = Field(..., alias='domains') - activitylogs: float = Field(..., alias='activityLogs') + activitylogs: Optional[float] = Field(default=None, alias='activityLogs') usagelogs: float = Field(..., alias='usageLogs') usagelogsintervals: Optional[List[Any]] = Field(default=None, alias='usageLogsIntervals') projectinactivitydays: float = Field(..., alias='projectInactivityDays') @@ -213,10 +213,10 @@ class BillingPlan(AppwriteModel): supportsfreeemailvalidation: bool = Field(..., alias='supportsFreeEmailValidation') supportscorporateemailvalidation: bool = Field(..., alias='supportsCorporateEmailValidation') supportsprojectspecificroles: bool = Field(..., alias='supportsProjectSpecificRoles') - backupsenabled: bool = Field(..., alias='backupsEnabled') + backupsenabled: Optional[bool] = Field(default=None, alias='backupsEnabled') usageperproject: bool = Field(..., alias='usagePerProject') supportedaddons: BillingPlanSupportedAddons = Field(..., alias='supportedAddons') - backuppolicies: float = Field(..., alias='backupPolicies') + backuppolicies: Optional[float] = Field(default=None, alias='backupPolicies') deploymentsize: float = Field(..., alias='deploymentSize') buildsize: float = Field(..., alias='buildSize') databasesallowencrypt: bool = Field(..., alias='databasesAllowEncrypt') diff --git a/appwrite/models/billing_plan_addon.py b/appwrite/models/billing_plan_addon.py index b1d75b34..6ad96a36 100644 --- a/appwrite/models/billing_plan_addon.py +++ b/appwrite/models/billing_plan_addon.py @@ -10,10 +10,10 @@ class BillingPlanAddon(AppwriteModel): Attributes ---------- - seats : BillingPlanAddonDetails + seats : Optional[BillingPlanAddonDetails] Addon seats - projects : BillingPlanAddonDetails + projects : Optional[BillingPlanAddonDetails] Addon projects """ - seats: BillingPlanAddonDetails = Field(..., alias='seats') - projects: BillingPlanAddonDetails = Field(..., alias='projects') + seats: Optional[BillingPlanAddonDetails] = Field(default=None, alias='seats') + projects: Optional[BillingPlanAddonDetails] = Field(default=None, alias='projects') diff --git a/appwrite/models/billing_plan_addon_details.py b/appwrite/models/billing_plan_addon_details.py index 8acd360c..67b2754a 100644 --- a/appwrite/models/billing_plan_addon_details.py +++ b/appwrite/models/billing_plan_addon_details.py @@ -17,7 +17,7 @@ class BillingPlanAddonDetails(AppwriteModel): Addon limit type : str Addon type - currency : str + currency : Optional[str] Price currency price : float Price @@ -30,7 +30,7 @@ class BillingPlanAddonDetails(AppwriteModel): planincluded: float = Field(..., alias='planIncluded') limit: float = Field(..., alias='limit') type: str = Field(..., alias='type') - currency: str = Field(..., alias='currency') + currency: Optional[str] = Field(default=None, alias='currency') price: float = Field(..., alias='price') value: float = Field(..., alias='value') invoicedesc: str = Field(..., alias='invoiceDesc') diff --git a/appwrite/models/database.py b/appwrite/models/database.py index c1852901..6c02040a 100644 --- a/appwrite/models/database.py +++ b/appwrite/models/database.py @@ -28,7 +28,7 @@ class Database(AppwriteModel): status : Optional[DatabaseStatus] Dedicated database lifecycle status. Null when the database has no valid dedicated backing. engine : Optional[str] - Underlying engine of the dedicated backing: postgresql, mysql, mariadb, or mongodb. A managed product (tablesdb, documentsdb, vectorsdb) reports the engine it runs on, so its type and engine can differ. Null when the database has no dedicated backing. + Underlying engine of the dedicated backing: postgresql, mysql, or mongodb. A managed product (tablesdb, documentsdb, vectorsdb) reports the engine it runs on, so its type and engine can differ. Null when the database has no dedicated backing. specification : Optional[str] Compute specification identifier of the dedicated backing, e.g. s-2vcpu-2gb. Null when the database has no dedicated backing. replicas : Optional[float] diff --git a/appwrite/models/database_migration.py b/appwrite/models/database_migration.py new file mode 100644 index 00000000..f1bcea3b --- /dev/null +++ b/appwrite/models/database_migration.py @@ -0,0 +1,60 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class DatabaseMigration(AppwriteModel): + """ + Database Migration + + Attributes + ---------- + id : str + Database migration ID. + createdat : str + Migration creation time in ISO 8601 format. + updatedat : str + Migration update time in ISO 8601 format. + projectid : str + Project ID that owns the migrating database. + databaseid : str + Logical database ID being migrated. + specification : str + Dedicated compute specification provisioned for the migration target. + phase : str + Migration phase. Possible values: pending, provisioned, capturing, backfilling, catching_up, verifying, ready_to_cutover, cutover, soaking, done, failed, rolled_back. + attempt : float + Number of times a migration step has failed and been recorded. + lasterror : str + Reason the most recent migration step failed, empty while none has. + lagdocuments : float + Number of documents still pending replication to the target. + verifiedat : str + Time the migrated data was verified against the source in ISO 8601 format. + cutoverat : str + Time routing was flipped to the target in ISO 8601 format. + soakuntil : str + Time the post-cutover soak window ends in ISO 8601 format. + autocutover : bool + Whether the migration cuts over automatically once ready. Set when the migration is created and never changed afterwards, so it always reports what was asked for. + cutoverrequested : bool + Whether a cutover has been requested and not yet attempted. Set by the cutover endpoint and cleared when the attempt is made, so a cutover that fails a check parks the migration again rather than retrying on its own. + paused : bool + Whether the migration is paused. + """ + id: str = Field(..., alias='$id') + createdat: str = Field(..., alias='$createdAt') + updatedat: str = Field(..., alias='$updatedAt') + projectid: str = Field(..., alias='projectId') + databaseid: str = Field(..., alias='databaseId') + specification: str = Field(..., alias='specification') + phase: str = Field(..., alias='phase') + attempt: float = Field(..., alias='attempt') + lasterror: str = Field(..., alias='lastError') + lagdocuments: float = Field(..., alias='lagDocuments') + verifiedat: str = Field(..., alias='verifiedAt') + cutoverat: str = Field(..., alias='cutoverAt') + soakuntil: str = Field(..., alias='soakUntil') + autocutover: bool = Field(..., alias='autoCutover') + cutoverrequested: bool = Field(..., alias='cutoverRequested') + paused: bool = Field(..., alias='paused') diff --git a/appwrite/models/database_migration_list.py b/appwrite/models/database_migration_list.py new file mode 100644 index 00000000..63571efb --- /dev/null +++ b/appwrite/models/database_migration_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .database_migration import DatabaseMigration + +class DatabaseMigrationList(AppwriteModel): + """ + Database Migrations List + + Attributes + ---------- + total : float + Total number of migrations that matched your query. + migrations : List[DatabaseMigration] + List of migrations. + """ + total: float = Field(..., alias='total') + migrations: List[DatabaseMigration] = Field(..., alias='migrations') diff --git a/appwrite/models/database_status.py b/appwrite/models/database_status.py index dd469917..34d3c1f6 100644 --- a/appwrite/models/database_status.py +++ b/appwrite/models/database_status.py @@ -13,19 +13,31 @@ class DatabaseStatus(AppwriteModel): Attributes ---------- health : str - Overall health status: healthy, degraded, or unhealthy. + Overall health status: healthy, degraded, unhealthy, or unknown when nothing could be measured. ready : bool Whether the database is ready to accept connections. engine : str - Database engine: postgresql, mysql, mariadb, or mongodb. + Database engine: postgresql, mysql, or mongodb. version : str Database engine version. uptime : float Database uptime in seconds. connections : DatabaseStatusConnections Connection statistics. + syncmode : str + Requested replication sync mode. Possible values: async, sync, quorum. Compare with effectiveSyncMode for what the primary is enforcing. + effectivesyncmode : Optional[str] + Replication sync mode the primary is actually enforcing. Null when high availability is disabled or the state could not be read. + syncdegraded : bool + Whether the enforced replication is weaker than the requested syncMode. + syncacknowledgements : float + Number of standby acknowledgements the primary waits for before a write is committed. + syncstandbycount : float + Number of standbys registered with the primary for synchronous replication. + syncstateconfirmed : Optional[bool] + Whether the other sync fields are an engine reading rather than a recorded estimate. True when the primary answered what it is enforcing, including when that answer contradicted the record, in which case the contradicted values are replaced by the ones the engine reports. False when the reading could not be taken: the probe did not answer, there was no engine to ask, or the values describe a configuration change just applied rather than anything measured. Absent when no engine was asked at all, so an unprobed database is distinguishable from an unconfirmed one. False never means a standby was found lagging, because it is the absence of a reading rather than a negative one, so draw no conclusion about replication health from it or from a response that omits it. replicas : List[DatabaseStatusReplica] - List of database replicas and their status. + List of database replicas and their status. Every configured member appears, including one the backend has not brought up, which is reported as not healthy. volumes : List[DatabaseStatusVolume] Storage volume information. """ @@ -35,5 +47,11 @@ class DatabaseStatus(AppwriteModel): version: str = Field(..., alias='version') uptime: float = Field(..., alias='uptime') connections: DatabaseStatusConnections = Field(..., alias='connections') + syncmode: str = Field(..., alias='syncMode') + effectivesyncmode: Optional[str] = Field(default=None, alias='effectiveSyncMode') + syncdegraded: bool = Field(..., alias='syncDegraded') + syncacknowledgements: float = Field(..., alias='syncAcknowledgements') + syncstandbycount: float = Field(..., alias='syncStandbyCount') + syncstateconfirmed: Optional[bool] = Field(default=None, alias='syncStateConfirmed') replicas: List[DatabaseStatusReplica] = Field(..., alias='replicas') volumes: List[DatabaseStatusVolume] = Field(..., alias='volumes') diff --git a/appwrite/models/database_status_connections.py b/appwrite/models/database_status_connections.py index fe335596..6cfa52e2 100644 --- a/appwrite/models/database_status_connections.py +++ b/appwrite/models/database_status_connections.py @@ -12,7 +12,7 @@ class DatabaseStatusConnections(AppwriteModel): current : float Current number of active connections. max : float - Maximum allowed connections. + The engine's own max_connections. On a pooled database this is the backend limit the pooler multiplexes onto, not the ceiling a client pool may reach — that is networkMaxConnections on the database resource. """ current: float = Field(..., alias='current') max: float = Field(..., alias='max') diff --git a/appwrite/models/database_status_replica.py b/appwrite/models/database_status_replica.py index 73cf79e4..91428e91 100644 --- a/appwrite/models/database_status_replica.py +++ b/appwrite/models/database_status_replica.py @@ -10,9 +10,9 @@ class DatabaseStatusReplica(AppwriteModel): Attributes ---------- index : float - StatefulSet pod index (0 = primary, 1+ = replicas). + Member index within the database. Read `role` for which member accepts writes: a failover moves the primary without renumbering the indexes. role : str - Replica role: primary or replica. + Member role. Possible values: primary (accepts reads and writes), replica (read-only follower), unknown (placement not established; reported while a transition is moving or restarting the topology, so no member can be named the write target). healthy : bool Whether the replica is healthy. lagseconds : Optional[float] diff --git a/appwrite/models/dedicated_database.py b/appwrite/models/dedicated_database.py index 9f943228..810fe7df 100644 --- a/appwrite/models/dedicated_database.py +++ b/appwrite/models/dedicated_database.py @@ -22,7 +22,7 @@ class DedicatedDatabase(AppwriteModel): api : str Product API that owns this database: tablesdb, documentsdb, vectorsdb, mysql, postgresql, or mongodb. engine : str - Database engine: postgresql, mysql, mariadb, or mongodb. + Database engine: postgresql, mysql, or mongodb. Null until the backing reports one. version : str Database engine version. specification : str @@ -32,7 +32,7 @@ class DedicatedDatabase(AppwriteModel): hostname : str Database hostname for connections. connectionport : float - Database port for connections. + Database port for connections. Derived from the engine when the backing has not reported one yet. connectionuser : str Database username for connections. connectionpassword : str @@ -69,10 +69,8 @@ class DedicatedDatabase(AppwriteModel): Number of high availability replicas. High availability is enabled when greater than 0. syncmode : str Replication sync mode: async, sync, or quorum. - crossregionreplicas : float - Number of cross-region replicas. Cross-region availability is enabled when greater than 0. networkmaxconnections : float - Maximum concurrent connections. + Maximum concurrent client connections. This is the limit a client pool may reach; the engine's own max_connections reported by the status endpoint is a smaller backend limit the pooler multiplexes onto and does not constrain a client pool. networkidletimeoutseconds : float Connection idle timeout in seconds. networkipallowlist : List[Any] @@ -138,7 +136,6 @@ class DedicatedDatabase(AppwriteModel): nodepool: str = Field(..., alias='nodePool') replicas: float = Field(..., alias='replicas') syncmode: str = Field(..., alias='syncMode') - crossregionreplicas: float = Field(..., alias='crossRegionReplicas') networkmaxconnections: float = Field(..., alias='networkMaxConnections') networkidletimeoutseconds: float = Field(..., alias='networkIdleTimeoutSeconds') networkipallowlist: List[Any] = Field(..., alias='networkIPAllowlist') diff --git a/appwrite/models/dedicated_database_member.py b/appwrite/models/dedicated_database_member.py index 6cb4daa3..128021bb 100644 --- a/appwrite/models/dedicated_database_member.py +++ b/appwrite/models/dedicated_database_member.py @@ -12,13 +12,13 @@ class DedicatedDatabaseMember(AppwriteModel): id : str Member identifier. role : str - Member role. Possible values: primary (accepts reads and writes), replica (read-only follower). + Member role. Possible values: primary (accepts reads and writes), replica (read-only follower), unknown (placement not established; reported while a transition is moving or restarting the topology and this member has not been probed, so no member can be named the write target). status : str - Member pod status. Possible values: provisioning (pod missing or Pending), starting (Running but not Ready), active (Running and Ready), failed (Failed phase or CrashLoopBackOff container), or the lowercased pod phase reported by the cluster. - lagseconds : float - Replication lag in seconds. + Member pod status. Possible values: pending (configured but absent from the backend topology, so nothing is bringing it up), provisioning (pod missing or Pending), starting (Running but not Ready), active (Running and Ready), failed (Failed phase or CrashLoopBackOff container), or the lowercased pod phase reported by the cluster. + lagseconds : Optional[float] + Replication lag in seconds. Null when the lag is not known: a primary has none to report, and a member the backend has not probed has none yet. """ id: str = Field(..., alias='$id') role: str = Field(..., alias='role') status: str = Field(..., alias='status') - lagseconds: float = Field(..., alias='lagSeconds') + lagseconds: Optional[float] = Field(default=None, alias='lagSeconds') diff --git a/appwrite/models/dedicated_database_operation.py b/appwrite/models/dedicated_database_operation.py new file mode 100644 index 00000000..97d482d3 --- /dev/null +++ b/appwrite/models/dedicated_database_operation.py @@ -0,0 +1,45 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class DedicatedDatabaseOperation(AppwriteModel): + """ + Operation + + Attributes + ---------- + id : str + Operation ID. + createdat : str + Operation creation time in ISO 8601 format. + 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. + status : str + Operation status. Possible values: running (in progress), completed (finished successfully), failed (ended in an error). + attempts : float + Number of times this operation has been attempted. + requestedat : Optional[str] + Time the operation was requested, in ISO 8601 format. + startedat : Optional[str] + Time the operation started, in ISO 8601 format. + completedat : Optional[str] + Time the operation reached a terminal state, in ISO 8601 format. + errorcode : str + Machine-readable failure code. `Interrupted` marks an attempt that ended before its outcome could be confirmed. + errormessage : str + Failure message if the operation failed. + """ + id: str = Field(..., alias='$id') + createdat: str = Field(..., alias='$createdAt') + databaseid: str = Field(..., alias='databaseId') + type: str = Field(..., alias='type') + status: str = Field(..., alias='status') + attempts: float = Field(..., alias='attempts') + requestedat: Optional[str] = Field(default=None, alias='requestedAt') + startedat: Optional[str] = Field(default=None, alias='startedAt') + completedat: Optional[str] = Field(default=None, alias='completedAt') + errorcode: str = Field(..., alias='errorCode') + errormessage: str = Field(..., alias='errorMessage') diff --git a/appwrite/models/dedicated_database_operation_list.py b/appwrite/models/dedicated_database_operation_list.py new file mode 100644 index 00000000..012e21d5 --- /dev/null +++ b/appwrite/models/dedicated_database_operation_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .dedicated_database_operation import DedicatedDatabaseOperation + +class DedicatedDatabaseOperationList(AppwriteModel): + """ + OperationList + + Attributes + ---------- + total : float + Total number of operations. + operations : List[DedicatedDatabaseOperation] + List of operations. + """ + total: float = Field(..., alias='total') + operations: List[DedicatedDatabaseOperation] = Field(..., alias='operations') diff --git a/appwrite/models/dedicated_database_replicas.py b/appwrite/models/dedicated_database_replicas.py index fa19a87a..45e6de40 100644 --- a/appwrite/models/dedicated_database_replicas.py +++ b/appwrite/models/dedicated_database_replicas.py @@ -13,10 +13,25 @@ class DedicatedDatabaseReplicas(AppwriteModel): replicas : float Number of configured replicas. Zero means high availability is disabled. syncmode : str - Replication sync mode. Possible values: async (asynchronous, fastest), sync (synchronous, strong consistency), quorum (quorum-based, majority of replicas must confirm). + Requested replication sync mode. Possible values: async (asynchronous, fastest), sync (synchronous, strong consistency), quorum (quorum-based, majority of replicas must confirm). This is what was asked for; compare it with effectiveSyncMode for what the primary is enforcing. + effectivesyncmode : Optional[str] + Replication sync mode the primary is actually enforcing. Null when high availability is disabled or the state could not be read. A value below the requested syncMode means writes are being acknowledged with weaker durability than configured. + syncdegraded : bool + Whether the enforced replication is weaker than the requested syncMode. + syncacknowledgements : float + Number of standby acknowledgements the primary waits for before a write is committed. Zero means writes are acknowledged locally. + syncstandbycount : float + Number of standbys registered with the primary for synchronous replication. + syncstateconfirmed : Optional[bool] + Whether the other sync fields are an engine reading rather than a recorded estimate. True when the primary answered what it is enforcing, including when that answer contradicted the record, in which case the contradicted values are replaced by the ones the engine reports. False when the reading could not be taken: the probe did not answer, there was no engine to ask, or the values describe a configuration change just applied rather than anything measured. Absent when no engine was asked at all, so an unprobed database is distinguishable from an unconfirmed one. False never means a standby was found lagging, because it is the absence of a reading rather than a negative one, so draw no conclusion about replication health from it or from a response that omits it. members : List[DedicatedDatabaseMember] Per-pod statuses for the primary and every replica. """ replicas: float = Field(..., alias='replicas') syncmode: str = Field(..., alias='syncMode') + effectivesyncmode: Optional[str] = Field(default=None, alias='effectiveSyncMode') + syncdegraded: bool = Field(..., alias='syncDegraded') + syncacknowledgements: float = Field(..., alias='syncAcknowledgements') + syncstandbycount: float = Field(..., alias='syncStandbyCount') + syncstateconfirmed: Optional[bool] = Field(default=None, alias='syncStateConfirmed') members: List[DedicatedDatabaseMember] = Field(..., alias='members') diff --git a/appwrite/models/dedicated_database_specification_pricing.py b/appwrite/models/dedicated_database_specification_pricing.py index 43d3b1e7..4c614a5a 100644 --- a/appwrite/models/dedicated_database_specification_pricing.py +++ b/appwrite/models/dedicated_database_specification_pricing.py @@ -15,13 +15,10 @@ class DedicatedDatabaseSpecificationPricing(AppwriteModel): Price per GB of bandwidth above the included amount, per month, in USD. replicarate : float High availability replica price as a fraction of the specification cost. - crossregionreplicarate : float - Cross-region replica price as a fraction of the specification cost. pitrrate : float Point-in-time recovery price as a fraction of the specification cost. """ storageoveragerate: float = Field(..., alias='storageOverageRate') bandwidthoveragerate: float = Field(..., alias='bandwidthOverageRate') replicarate: float = Field(..., alias='replicaRate') - crossregionreplicarate: float = Field(..., alias='crossRegionReplicaRate') pitrrate: float = Field(..., alias='pitrRate') diff --git a/appwrite/models/document.py b/appwrite/models/document.py index 4df880fc..b0ae18f4 100644 --- a/appwrite/models/document.py +++ b/appwrite/models/document.py @@ -1,10 +1,12 @@ from typing import Any, Dict, List, Optional, Union, cast, Generic, TypeVar, Type -from pydantic import Field, PrivateAttr +from pydantic import Field, PrivateAttr, TypeAdapter, model_serializer from .base_model import AppwriteModel T = TypeVar('T') +_PAYLOAD_ADAPTER = TypeAdapter(Dict[str, Any]) + class Document(AppwriteModel, Generic[T]): """ Document @@ -57,24 +59,63 @@ def data(self) -> T: def data(self, value: T) -> None: object.__setattr__(self, '_data', value) - def model_dump(self, **kwargs) -> Dict[str, Any]: - result = super().model_dump(**kwargs) - if hasattr(self, '_data'): - if isinstance(self._data, dict): - result['data'] = self._data - elif hasattr(self._data, 'model_dump'): - result['data'] = self._data.model_dump(**kwargs) - else: - result['data'] = self._data - return result + def _serialize_data(self, info, include=None, exclude=None): + if hasattr(self._data, 'model_dump'): + return self._data.model_dump( + mode=info.mode, + by_alias=info.by_alias, + exclude_unset=info.exclude_unset, + exclude_defaults=info.exclude_defaults, + exclude_none=info.exclude_none, + include=include, + exclude=exclude, + ) + + if isinstance(self._data, dict) and (include is not None or exclude is not None): + return _PAYLOAD_ADAPTER.dump_python( + self._data, + mode=info.mode, + by_alias=info.by_alias, + exclude_unset=info.exclude_unset, + exclude_defaults=info.exclude_defaults, + exclude_none=info.exclude_none, + include=include, + exclude=exclude, + ) + + return self._data + + @staticmethod + def _select_data(selector): + """ + Resolves a pydantic include/exclude selector against the 'data' key, which is + serialized here rather than declared as a field. Returns whether the key was + named, and any nested selector to apply within it. + """ + if selector is None: + return False, None + + if isinstance(selector, dict): + if 'data' not in selector: + return False, None + + nested = selector['data'] + + return True, nested if isinstance(nested, (dict, set, frozenset, list, tuple)) else None + + return 'data' in selector, None + + @model_serializer(mode='wrap') + def _serialize_model(self, handler, info): + result = handler(self) + included, include_fields = self._select_data(info.include) + excluded, exclude_fields = self._select_data(info.exclude) + + if info.include is not None and not included: + return result + + if excluded and exclude_fields is None: + return result - def to_dict(self) -> Dict[str, Any]: - result = super().to_dict() - if hasattr(self, '_data'): - if isinstance(self._data, dict): - result['data'] = self._data - elif hasattr(self._data, 'model_dump'): - result['data'] = self._data.model_dump(mode='json') - else: - result['data'] = self._data + result['data'] = self._serialize_data(info, include_fields, exclude_fields) return result diff --git a/appwrite/models/document_list.py b/appwrite/models/document_list.py index a73bb75c..728c8039 100644 --- a/appwrite/models/document_list.py +++ b/appwrite/models/document_list.py @@ -26,7 +26,7 @@ def with_data(cls, data: Dict[str, Any], model_type: Type[T] = dict) -> 'Documen instance = cls.model_validate(data) if 'documents' in data and data['documents'] is not None: instance.documents = [ - Document.with_data(row, model_type) + Document.with_data(row, model_type) for row in data['documents'] ] return instance diff --git a/appwrite/models/embedding.py b/appwrite/models/embedding.py new file mode 100644 index 00000000..9da9bab5 --- /dev/null +++ b/appwrite/models/embedding.py @@ -0,0 +1,24 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class Embedding(AppwriteModel): + """ + Embedding + + Attributes + ---------- + model : str + Embedding model used to generate embeddings. + dimension : float + Number of dimensions for each embedding vector. + embedding : List[Any] + Embedding vector values. If an error occurs, this will be an empty array. + error : str + Error message if embedding generation fails. Empty string if no error. + """ + model: str = Field(..., alias='model') + dimension: float = Field(..., alias='dimension') + embedding: List[Any] = Field(..., alias='embedding') + error: str = Field(..., alias='error') diff --git a/appwrite/models/embedding_list.py b/appwrite/models/embedding_list.py new file mode 100644 index 00000000..d8f437e7 --- /dev/null +++ b/appwrite/models/embedding_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .embedding import Embedding + +class EmbeddingList(AppwriteModel): + """ + Embedding list + + Attributes + ---------- + total : float + Total number of embeddings that matched your query. + embeddings : List[Embedding] + List of embeddings. + """ + total: float = Field(..., alias='total') + embeddings: List[Embedding] = Field(..., alias='embeddings') diff --git a/appwrite/models/file.py b/appwrite/models/file.py index a49ee806..3657b451 100644 --- a/appwrite/models/file.py +++ b/appwrite/models/file.py @@ -21,6 +21,10 @@ class File(AppwriteModel): File permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). name : str File name. + folder : str + Virtual folder containing the file, with a trailing slash. Empty for the bucket root. + key : str + Full virtual path of the file: the folder followed by the file name. signature : str File MD5 signature. mimetype : str @@ -44,6 +48,8 @@ class File(AppwriteModel): updatedat: str = Field(..., alias='$updatedAt') permissions: List[Any] = Field(..., alias='$permissions') name: str = Field(..., alias='name') + folder: str = Field(..., alias='folder') + key: str = Field(..., alias='key') signature: str = Field(..., alias='signature') mimetype: str = Field(..., alias='mimeType') sizeoriginal: float = Field(..., alias='sizeOriginal') diff --git a/appwrite/models/mfa_challenge_secret.py b/appwrite/models/mfa_challenge_secret.py new file mode 100644 index 00000000..89beb284 --- /dev/null +++ b/appwrite/models/mfa_challenge_secret.py @@ -0,0 +1,27 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class MfaChallengeSecret(AppwriteModel): + """ + MFA Challenge Secret + + Attributes + ---------- + id : str + Token ID. + createdat : str + Token creation date in ISO 8601 format. + userid : str + User ID. + expire : str + Token expiration date in ISO 8601 format. + code : str + Challenge code to be delivered to the end user through a custom channel. + """ + id: str = Field(..., alias='$id') + createdat: str = Field(..., alias='$createdAt') + userid: str = Field(..., alias='userId') + expire: str = Field(..., alias='expire') + code: str = Field(..., alias='code') diff --git a/appwrite/models/mfa_factors.py b/appwrite/models/mfa_factors.py index 1392c1d2..2d6953a1 100644 --- a/appwrite/models/mfa_factors.py +++ b/appwrite/models/mfa_factors.py @@ -17,8 +17,11 @@ class MfaFactors(AppwriteModel): Can email be used for MFA challenge for this account. recoverycode : bool Can recovery code be used for MFA challenge for this account. + custom : bool + Can custom factor be used for MFA challenge for this account. """ totp: bool = Field(..., alias='totp') phone: bool = Field(..., alias='phone') email: bool = Field(..., alias='email') recoverycode: bool = Field(..., alias='recoveryCode') + custom: bool = Field(..., alias='custom') diff --git a/appwrite/models/organization.py b/appwrite/models/organization.py index 95f4bdef..604be7d5 100644 --- a/appwrite/models/organization.py +++ b/appwrite/models/organization.py @@ -26,8 +26,8 @@ class Organization(AppwriteModel, Generic[T]): Total number of team members. prefs : Preferences[T] Team preferences as a key-value object - billingbudget : float - Project budget limit + billingbudget : Optional[float] + Project budget limit. Null when no budget is set. budgetalerts : List[Any] Project budget limit billingplan : str @@ -44,7 +44,7 @@ class Organization(AppwriteModel, Generic[T]): Current invoice cycle start date. billingnextinvoicedate : str Next invoice cycle start date. - billingtrialstartdate : str + billingtrialstartdate : Optional[str] Start date of trial. billingtrialdays : float Number of trial days. @@ -54,29 +54,29 @@ class Organization(AppwriteModel, Generic[T]): Current active aggregation id. paymentmethodid : str Default payment method. - billingaddressid : str + billingaddressid : Optional[str] Default payment method. - backuppaymentmethodid : str + backuppaymentmethodid : Optional[str] Backup payment method. status : str Team status. - remarks : str + remarks : Optional[str] Remarks on team status. - agreementbaa : str + agreementbaa : Optional[str] Organization agreements - programmanagername : str + programmanagername : Optional[str] Program manager's name. - programmanagercalendar : str + programmanagercalendar : Optional[str] Program manager's calendar link. - programdiscordchannelname : str + programdiscordchannelname : Optional[str] Program's discord channel name. - programdiscordchannelurl : str + programdiscordchannelurl : Optional[str] Program's discord channel URL. billinglimits : Optional[BillingLimits] Billing limits reached - billingplandowngrade : str + billingplandowngrade : Optional[str] Billing plan selected for downgrade. - billingtaxid : str + billingtaxid : Optional[str] Tax Id markedfordeletion : bool Marked for deletion @@ -91,7 +91,7 @@ class Organization(AppwriteModel, Generic[T]): name: str = Field(..., alias='name') total: float = Field(..., alias='total') prefs: Preferences[T] = Field(..., alias='prefs') - billingbudget: float = Field(..., alias='billingBudget') + billingbudget: Optional[float] = Field(default=None, alias='billingBudget') budgetalerts: List[Any] = Field(..., alias='budgetAlerts') billingplan: str = Field(..., alias='billingPlan') billingplanid: str = Field(..., alias='billingPlanId') @@ -100,23 +100,23 @@ class Organization(AppwriteModel, Generic[T]): billingstartdate: str = Field(..., alias='billingStartDate') billingcurrentinvoicedate: str = Field(..., alias='billingCurrentInvoiceDate') billingnextinvoicedate: str = Field(..., alias='billingNextInvoiceDate') - billingtrialstartdate: str = Field(..., alias='billingTrialStartDate') + billingtrialstartdate: Optional[str] = Field(default=None, alias='billingTrialStartDate') billingtrialdays: float = Field(..., alias='billingTrialDays') billingaggregationid: str = Field(..., alias='billingAggregationId') billinginvoiceid: str = Field(..., alias='billingInvoiceId') paymentmethodid: str = Field(..., alias='paymentMethodId') - billingaddressid: str = Field(..., alias='billingAddressId') - backuppaymentmethodid: str = Field(..., alias='backupPaymentMethodId') + billingaddressid: Optional[str] = Field(default=None, alias='billingAddressId') + backuppaymentmethodid: Optional[str] = Field(default=None, alias='backupPaymentMethodId') status: str = Field(..., alias='status') - remarks: str = Field(..., alias='remarks') - agreementbaa: str = Field(..., alias='agreementBAA') - programmanagername: str = Field(..., alias='programManagerName') - programmanagercalendar: str = Field(..., alias='programManagerCalendar') - programdiscordchannelname: str = Field(..., alias='programDiscordChannelName') - programdiscordchannelurl: str = Field(..., alias='programDiscordChannelUrl') + remarks: Optional[str] = Field(default=None, alias='remarks') + agreementbaa: Optional[str] = Field(default=None, alias='agreementBAA') + programmanagername: Optional[str] = Field(default=None, alias='programManagerName') + programmanagercalendar: Optional[str] = Field(default=None, alias='programManagerCalendar') + programdiscordchannelname: Optional[str] = Field(default=None, alias='programDiscordChannelName') + programdiscordchannelurl: Optional[str] = Field(default=None, alias='programDiscordChannelUrl') billinglimits: Optional[BillingLimits] = Field(default=None, alias='billingLimits') - billingplandowngrade: str = Field(..., alias='billingPlanDowngrade') - billingtaxid: str = Field(..., alias='billingTaxId') + billingplandowngrade: Optional[str] = Field(default=None, alias='billingPlanDowngrade') + billingtaxid: Optional[str] = Field(default=None, alias='billingTaxId') markedfordeletion: bool = Field(..., alias='markedForDeletion') platform: str = Field(..., alias='platform') projects: List[Any] = Field(..., alias='projects') diff --git a/appwrite/models/policy_list.py b/appwrite/models/policy_list.py index 3e579817..f00999b3 100644 --- a/appwrite/models/policy_list.py +++ b/appwrite/models/policy_list.py @@ -12,6 +12,7 @@ from .policy_session_limit import PolicySessionLimit from .policy_user_limit import PolicyUserLimit from .policy_membership_privacy import PolicyMembershipPrivacy +from .policy_mfa_factors import PolicyMfaFactors from .policy_deny_aliased_email import PolicyDenyAliasedEmail from .policy_deny_disposable_email import PolicyDenyDisposableEmail from .policy_deny_free_email import PolicyDenyFreeEmail @@ -25,8 +26,8 @@ class PolicyList(AppwriteModel): ---------- total : float Total number of policies in the given project. - policies : List[Union[PolicyPasswordDictionary, PolicyPasswordHistory, PolicyPasswordStrength, PolicyPasswordPersonalData, PolicySessionAlert, PolicySessionDuration, PolicySessionInvalidation, PolicySessionLimit, PolicyUserLimit, PolicyMembershipPrivacy, PolicyDenyAliasedEmail, PolicyDenyDisposableEmail, PolicyDenyFreeEmail, PolicyDenyCorporateEmail]] + policies : List[Union[PolicyPasswordDictionary, PolicyPasswordHistory, PolicyPasswordStrength, PolicyPasswordPersonalData, PolicySessionAlert, PolicySessionDuration, PolicySessionInvalidation, PolicySessionLimit, PolicyUserLimit, PolicyMembershipPrivacy, PolicyMfaFactors, PolicyDenyAliasedEmail, PolicyDenyDisposableEmail, PolicyDenyFreeEmail, PolicyDenyCorporateEmail]] List of policies. """ total: float = Field(..., alias='total') - policies: List[Union[PolicyPasswordDictionary, PolicyPasswordHistory, PolicyPasswordStrength, PolicyPasswordPersonalData, PolicySessionAlert, PolicySessionDuration, PolicySessionInvalidation, PolicySessionLimit, PolicyUserLimit, PolicyMembershipPrivacy, PolicyDenyAliasedEmail, PolicyDenyDisposableEmail, PolicyDenyFreeEmail, PolicyDenyCorporateEmail]] = Field(..., alias='policies') + policies: List[Union[PolicyPasswordDictionary, PolicyPasswordHistory, PolicyPasswordStrength, PolicyPasswordPersonalData, PolicySessionAlert, PolicySessionDuration, PolicySessionInvalidation, PolicySessionLimit, PolicyUserLimit, PolicyMembershipPrivacy, PolicyMfaFactors, PolicyDenyAliasedEmail, PolicyDenyDisposableEmail, PolicyDenyFreeEmail, PolicyDenyCorporateEmail]] = Field(..., alias='policies') diff --git a/appwrite/models/policy_mfa_factors.py b/appwrite/models/policy_mfa_factors.py new file mode 100644 index 00000000..8ff59740 --- /dev/null +++ b/appwrite/models/policy_mfa_factors.py @@ -0,0 +1,27 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class PolicyMfaFactors(AppwriteModel): + """ + Policy MFA Factors + + Attributes + ---------- + id : str + Policy ID. + totp : bool + Whether TOTP can be used to complete an MFA challenge. + email : bool + Whether email can be used to complete an MFA challenge. + phone : bool + Whether phone (SMS) can be used to complete an MFA challenge. + custom : bool + Whether the custom factor can be used to complete an MFA challenge. + """ + id: str = Field(..., alias='$id') + totp: bool = Field(..., alias='totp') + email: bool = Field(..., alias='email') + phone: bool = Field(..., alias='phone') + custom: bool = Field(..., alias='custom') diff --git a/appwrite/models/preferences.py b/appwrite/models/preferences.py index 73a68a21..5d655bd1 100644 --- a/appwrite/models/preferences.py +++ b/appwrite/models/preferences.py @@ -1,10 +1,12 @@ from typing import Any, Dict, List, Optional, Union, cast, Generic, TypeVar, Type -from pydantic import Field, PrivateAttr +from pydantic import Field, PrivateAttr, TypeAdapter, model_serializer from .base_model import AppwriteModel T = TypeVar('T') +_PAYLOAD_ADAPTER = TypeAdapter(Dict[str, Any]) + class Preferences(AppwriteModel, Generic[T]): """ Preferences @@ -29,24 +31,37 @@ def data(self) -> T: def data(self, value: T) -> None: object.__setattr__(self, '_data', value) - def model_dump(self, **kwargs) -> Dict[str, Any]: - result = super().model_dump(**kwargs) - if hasattr(self, '_data'): - if isinstance(self._data, dict): - result['data'] = self._data - elif hasattr(self._data, 'model_dump'): - result['data'] = self._data.model_dump(**kwargs) - else: - result['data'] = self._data - return result - - def to_dict(self) -> Dict[str, Any]: - result = super().to_dict() - if hasattr(self, '_data'): - if isinstance(self._data, dict): - result['data'] = self._data - elif hasattr(self._data, 'model_dump'): - result['data'] = self._data.model_dump(mode='json') - else: - result['data'] = self._data - return result + def _serialize_data(self, info, include=None, exclude=None): + if hasattr(self._data, 'model_dump'): + return self._data.model_dump( + mode=info.mode, + by_alias=info.by_alias, + exclude_unset=info.exclude_unset, + exclude_defaults=info.exclude_defaults, + exclude_none=info.exclude_none, + include=include, + exclude=exclude, + ) + + if isinstance(self._data, dict) and (include is not None or exclude is not None): + return _PAYLOAD_ADAPTER.dump_python( + self._data, + mode=info.mode, + by_alias=info.by_alias, + exclude_unset=info.exclude_unset, + exclude_defaults=info.exclude_defaults, + exclude_none=info.exclude_none, + include=include, + exclude=exclude, + ) + + return self._data + + @model_serializer(mode='wrap') + def _serialize_model(self, handler, info): + result = handler(self) + data = self._serialize_data(info, info.include, info.exclude) + if isinstance(result, dict) and isinstance(data, dict): + return {**result, **data} + + return data diff --git a/appwrite/models/project.py b/appwrite/models/project.py index 76822354..bc9abb12 100644 --- a/appwrite/models/project.py +++ b/appwrite/models/project.py @@ -81,6 +81,8 @@ class Project(AppwriteModel): OAuth2 server allowed scopes oauth2serverdefaultscopes : Optional[List[Any]] OAuth2 server scopes used when an authorization request omits the scope parameter + oauth2serverinstallationscopes : Optional[List[Any]] + Scopes an application may request when installed on a team oauth2serverauthorizationdetailstypes : Optional[List[Any]] OAuth2 server accepted RFC 9396 authorization_details types oauth2serveraccesstokenduration : Optional[float] @@ -139,6 +141,7 @@ class Project(AppwriteModel): oauth2serverauthorizationurl: Optional[str] = Field(default=None, alias='oAuth2ServerAuthorizationUrl') oauth2serverscopes: Optional[List[Any]] = Field(default=None, alias='oAuth2ServerScopes') oauth2serverdefaultscopes: Optional[List[Any]] = Field(default=None, alias='oAuth2ServerDefaultScopes') + oauth2serverinstallationscopes: Optional[List[Any]] = Field(default=None, alias='oAuth2ServerInstallationScopes') oauth2serverauthorizationdetailstypes: Optional[List[Any]] = Field(default=None, alias='oAuth2ServerAuthorizationDetailsTypes') oauth2serveraccesstokenduration: Optional[float] = Field(default=None, alias='oAuth2ServerAccessTokenDuration') oauth2serverrefreshtokenduration: Optional[float] = Field(default=None, alias='oAuth2ServerRefreshTokenDuration') diff --git a/appwrite/models/proxy_invalidation.py b/appwrite/models/proxy_invalidation.py new file mode 100644 index 00000000..511a0feb --- /dev/null +++ b/appwrite/models/proxy_invalidation.py @@ -0,0 +1,24 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class ProxyInvalidation(AppwriteModel): + """ + Invalidation + + Attributes + ---------- + domain : str + Domain name. + type : str + Invalidation type. Possible values are "tag", "path", or "all". + reference : str + Invalidated reference. Depending on type this is a cache tag name, a URL path, or empty when type is all. + status : str + Invalidation status. + """ + domain: str = Field(..., alias='domain') + type: str = Field(..., alias='type') + reference: str = Field(..., alias='reference') + status: str = Field(..., alias='status') diff --git a/appwrite/models/row.py b/appwrite/models/row.py index e6e15976..f421cd17 100644 --- a/appwrite/models/row.py +++ b/appwrite/models/row.py @@ -1,10 +1,12 @@ from typing import Any, Dict, List, Optional, Union, cast, Generic, TypeVar, Type -from pydantic import Field, PrivateAttr +from pydantic import Field, PrivateAttr, TypeAdapter, model_serializer from .base_model import AppwriteModel T = TypeVar('T') +_PAYLOAD_ADAPTER = TypeAdapter(Dict[str, Any]) + class Row(AppwriteModel, Generic[T]): """ Row @@ -57,24 +59,63 @@ def data(self) -> T: def data(self, value: T) -> None: object.__setattr__(self, '_data', value) - def model_dump(self, **kwargs) -> Dict[str, Any]: - result = super().model_dump(**kwargs) - if hasattr(self, '_data'): - if isinstance(self._data, dict): - result['data'] = self._data - elif hasattr(self._data, 'model_dump'): - result['data'] = self._data.model_dump(**kwargs) - else: - result['data'] = self._data - return result + def _serialize_data(self, info, include=None, exclude=None): + if hasattr(self._data, 'model_dump'): + return self._data.model_dump( + mode=info.mode, + by_alias=info.by_alias, + exclude_unset=info.exclude_unset, + exclude_defaults=info.exclude_defaults, + exclude_none=info.exclude_none, + include=include, + exclude=exclude, + ) + + if isinstance(self._data, dict) and (include is not None or exclude is not None): + return _PAYLOAD_ADAPTER.dump_python( + self._data, + mode=info.mode, + by_alias=info.by_alias, + exclude_unset=info.exclude_unset, + exclude_defaults=info.exclude_defaults, + exclude_none=info.exclude_none, + include=include, + exclude=exclude, + ) + + return self._data + + @staticmethod + def _select_data(selector): + """ + Resolves a pydantic include/exclude selector against the 'data' key, which is + serialized here rather than declared as a field. Returns whether the key was + named, and any nested selector to apply within it. + """ + if selector is None: + return False, None + + if isinstance(selector, dict): + if 'data' not in selector: + return False, None + + nested = selector['data'] + + return True, nested if isinstance(nested, (dict, set, frozenset, list, tuple)) else None + + return 'data' in selector, None + + @model_serializer(mode='wrap') + def _serialize_model(self, handler, info): + result = handler(self) + included, include_fields = self._select_data(info.include) + excluded, exclude_fields = self._select_data(info.exclude) + + if info.include is not None and not included: + return result + + if excluded and exclude_fields is None: + return result - def to_dict(self) -> Dict[str, Any]: - result = super().to_dict() - if hasattr(self, '_data'): - if isinstance(self._data, dict): - result['data'] = self._data - elif hasattr(self._data, 'model_dump'): - result['data'] = self._data.model_dump(mode='json') - else: - result['data'] = self._data + result['data'] = self._serialize_data(info, include_fields, exclude_fields) return result diff --git a/appwrite/models/row_list.py b/appwrite/models/row_list.py index 86ca37ad..b4508b2a 100644 --- a/appwrite/models/row_list.py +++ b/appwrite/models/row_list.py @@ -26,7 +26,7 @@ def with_data(cls, data: Dict[str, Any], model_type: Type[T] = dict) -> 'RowList instance = cls.model_validate(data) if 'rows' in data and data['rows'] is not None: instance.rows = [ - Row.with_data(row, model_type) + Row.with_data(row, model_type) for row in data['rows'] ] return instance diff --git a/appwrite/models/team_list.py b/appwrite/models/team_list.py index 81602c9b..85610b5c 100644 --- a/appwrite/models/team_list.py +++ b/appwrite/models/team_list.py @@ -26,7 +26,7 @@ def with_data(cls, data: Dict[str, Any], model_type: Type[T] = dict) -> 'TeamLis instance = cls.model_validate(data) if 'teams' in data and data['teams'] is not None: instance.teams = [ - Team.with_data(row, model_type) + Team.with_data(row, model_type) for row in data['teams'] ] return instance diff --git a/appwrite/models/usage_billing_plan.py b/appwrite/models/usage_billing_plan.py index a078e726..40016c29 100644 --- a/appwrite/models/usage_billing_plan.py +++ b/appwrite/models/usage_billing_plan.py @@ -14,13 +14,13 @@ class UsageBillingPlan(AppwriteModel): Bandwidth additional resources executions : AdditionalResource Executions additional resources - member : AdditionalResource + member : Optional[AdditionalResource] Member additional resources realtime : AdditionalResource Realtime additional resources realtimemessages : AdditionalResource Realtime messages additional resources - realtimebandwidth : AdditionalResource + realtimebandwidth : Optional[AdditionalResource] Realtime bandwidth additional resources storage : AdditionalResource Storage additional resources @@ -30,17 +30,17 @@ class UsageBillingPlan(AppwriteModel): GBHour additional resources imagetransformations : AdditionalResource Image transformation additional resources - credits : AdditionalResource + credits : Optional[AdditionalResource] Credits additional resources """ bandwidth: AdditionalResource = Field(..., alias='bandwidth') executions: AdditionalResource = Field(..., alias='executions') - member: AdditionalResource = Field(..., alias='member') + member: Optional[AdditionalResource] = Field(default=None, alias='member') realtime: AdditionalResource = Field(..., alias='realtime') realtimemessages: AdditionalResource = Field(..., alias='realtimeMessages') - realtimebandwidth: AdditionalResource = Field(..., alias='realtimeBandwidth') + realtimebandwidth: Optional[AdditionalResource] = Field(default=None, alias='realtimeBandwidth') storage: AdditionalResource = Field(..., alias='storage') users: AdditionalResource = Field(..., alias='users') gbhours: AdditionalResource = Field(..., alias='GBHours') imagetransformations: AdditionalResource = Field(..., alias='imageTransformations') - credits: AdditionalResource = Field(..., alias='credits') + credits: Optional[AdditionalResource] = Field(default=None, alias='credits') diff --git a/appwrite/models/user_list.py b/appwrite/models/user_list.py index c6c2e627..fa071b9c 100644 --- a/appwrite/models/user_list.py +++ b/appwrite/models/user_list.py @@ -26,7 +26,7 @@ def with_data(cls, data: Dict[str, Any], model_type: Type[T] = dict) -> 'UserLis instance = cls.model_validate(data) if 'users' in data and data['users'] is not None: instance.users = [ - User.with_data(row, model_type) + User.with_data(row, model_type) for row in data['users'] ] return instance diff --git a/appwrite/service.py b/appwrite/service.py index dcf43a08..fef16a05 100644 --- a/appwrite/service.py +++ b/appwrite/service.py @@ -46,5 +46,6 @@ def _parse_response( return model.model_validate(response) except ValidationError as error: raise AppwriteException( - f'Unable to parse response into {model.__name__}: {error}' + f'Unable to parse response into {model.__name__}: {error}', + response=response, ) from error diff --git a/appwrite/services/account.py b/appwrite/services/account.py index 492d9102..ee1f0f96 100644 --- a/appwrite/services/account.py +++ b/appwrite/services/account.py @@ -9,7 +9,6 @@ from ..models.oauth2_consent_token_list import Oauth2ConsentTokenList from ..models.oauth2_consent_token import Oauth2ConsentToken from ..models.identity_list import IdentityList -from ..models.jwt import Jwt from ..models.log_list import LogList from ..enums.authenticator_type import AuthenticatorType from ..models.mfa_type import MfaType @@ -525,44 +524,6 @@ def delete_identity( return response - def create_jwt( - self, - duration: Optional[float] = None - ) -> Jwt: - """ - Use this endpoint to create a JSON Web Token. You can use the resulting JWT to authenticate on behalf of the current user when working with the Appwrite server-side API and SDKs. The JWT secret is valid for 15 minutes from its creation and will be invalid if the user will logout in that time frame. - - Parameters - ---------- - duration : Optional[float] - Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds. - - Returns - ------- - Jwt - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/account/jwts' - api_params = {} - - if duration is not None: - api_params['duration'] = self._normalize_value(duration) - - response = self.client.call('post', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'content-type': 'application/json', - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=Jwt) - - def list_logs( self, queries: Optional[List[str]] = None, @@ -789,7 +750,7 @@ def create_mfa_challenge( Parameters ---------- factor : AuthenticationFactor - Factor used for verification. Must be one of following: `email`, `phone`, `totp`, `recoveryCode`. + Factor used for verification. Must be one of following: `email`, `phone`, `totp`, `recoveryCode`, `custom`. Returns ------- diff --git a/appwrite/services/activities.py b/appwrite/services/activities.py index 0d1e5b5b..a534716d 100644 --- a/appwrite/services/activities.py +++ b/appwrite/services/activities.py @@ -13,14 +13,14 @@ def __init__(self, client) -> None: def list_events( self, - queries: Optional[str] = None + queries: Optional[List[str]] = None ) -> ActivityEventList: """ List all events for selected filters. Parameters ---------- - queries : Optional[str] + queries : Optional[List[str]] Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on attributes such as userId, teamId, etc. Returns diff --git a/appwrite/services/apps.py b/appwrite/services/apps.py index d4a3f681..bd0e6f90 100644 --- a/appwrite/services/apps.py +++ b/appwrite/services/apps.py @@ -262,7 +262,7 @@ def get( Parameters ---------- app_id : str - Application unique ID or HTTPS client ID metadata document URL. + Application unique ID. Returns ------- @@ -356,7 +356,7 @@ def update( device_flow : Optional[bool] Allow this client to use the OAuth2 Device Authorization Grant (RFC 8628) for input-constrained devices such as TVs and CLIs. Defaults to false. installation_scopes : Optional[List[str]] - Scopes the application requests when installed on a team. Organization-level and project-level scopes only; use the list scopes endpoint with `type=installation` to discover available values. Maximum of 100 scopes are allowed. + Scopes the application requests when installed on a team. Only scopes allowed by the project's OAuth2 server installation scopes configuration are accepted; use the list installation scopes endpoint to discover available values. Maximum of 100 scopes are allowed. installation_redirect_url : Optional[str] URL users are redirected to after creating or updating an installation of this application. Must be an https URL, an http loopback URL (localhost, 127.0.0.1, [::1]), or a private-use scheme URI, and must not contain a fragment. Leave empty for no redirect. @@ -475,7 +475,7 @@ def list_installations( total: Optional[bool] = None ) -> AppInstallationList: """ - List installations of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. + List installations of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller with update access to the app. Parameters ---------- @@ -523,7 +523,7 @@ def get_installation( installation_id: str ) -> AppInstallation: """ - Get an installation of an application by its unique ID. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. + Get an installation of an application by its unique ID. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller with update access to the app. Parameters ---------- @@ -563,13 +563,60 @@ def get_installation( return self._parse_response(response, model=AppInstallation) + def delete_installation( + self, + app_id: str, + installation_id: str + ) -> Dict[str, Any]: + """ + Delete an installation of an application by its unique ID. Requires a caller with update access to the app. Previously issued installation access tokens are revoked. + + Parameters + ---------- + app_id : str + Application unique ID. + installation_id : str + Installation unique ID. + + Returns + ------- + Dict[str, Any] + API response as a dictionary + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/installations/{installationId}' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('delete', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return response + + def create_installation_token( self, app_id: str, installation_id: str ) -> Oauth2Token: """ - Create a token for an installation of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. The returned token carries the scopes and authorization details granted to the installation, and can be used as an `Authorization: Bearer` header everywhere OAuth2 access tokens are accepted. Multiple tokens can be active for the same installation at once; each token stays valid until it expires or the installation is updated or deleted. + Create a token for an installation of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller with update access to the app. The returned token carries the scopes and authorization details granted to the installation, and can be used as an `Authorization: Bearer` header everywhere OAuth2 access tokens are accepted. Multiple tokens can be active for the same installation at once; each token stays valid until it expires or the installation is updated or deleted. Parameters ---------- diff --git a/appwrite/services/backups.py b/appwrite/services/backups.py index c8b782c5..cc41ff09 100644 --- a/appwrite/services/backups.py +++ b/appwrite/services/backups.py @@ -439,13 +439,13 @@ def create_restoration( """ Create and trigger a new restoration for a backup on a project. - For a backup of one database, the restoration resolves its destination before it is queued. Pass `newResourceId` to restore into that database ID, including the archived database ID to overwrite it. When `newResourceId` is omitted, a new database ID is generated and returned in `options`. + For a backup of one database, the restoration resolves its destination before it is queued. When `newResourceId` is omitted, the archived database is restored in place and its own ID is returned in `options`. Pass a different `newResourceId` to restore alongside it as a new database instead. The restoration migration records the archived database in `resourceId` and `resourceType`, and the resolved database in `destinationResourceId` and `destinationResourceType`. Database types are stored canonically as `database`, `documentsdb`, or `vectorsdb`. Project-wide restorations leave these fields empty because they do not have a single source or destination database. To list every migration related to one database, use its canonical type in a nested `OR(AND(...), AND(...), AND(...))` across the root, parent, and destination relation pairs: `(resourceType, resourceId)`, `(parentResourceType, parentResourceId)`, and `(destinationResourceType, destinationResourceId)`. Legacy and TablesDB databases use `database`; the operational `resourceType` of a table migration is not rewritten to `tablesdb`. - When restoring a DocumentsDB or VectorsDB database to a new resource from a dedicated source, the restore provisions a fresh dedicated backing database at the source database's own specification. + When restoring a DocumentsDB or VectorsDB database from a dedicated source, the restore provisions a fresh dedicated backing database at the source database's own specification and lands the data there. An in-place restore swaps the database onto that backing only once the restore has succeeded, and retires the backing it displaced only once that swap is confirmed, so the source keeps serving its own data until the restored data is in place and any failure leaves it untouched. A serverless source has no dedicated backing to clone and restores onto the archived database instead. Parameters @@ -455,7 +455,7 @@ def create_restoration( services : List[BackupServices] Array of services to restore new_resource_id : Optional[str] - Destination resource ID. Omit to generate a new ID, or pass the archived resource ID to overwrite it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. + Destination resource ID. Omit to restore the archived resource in place, or pass a different ID to restore alongside it as a new resource. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. new_resource_name : Optional[str] Database name. Max length: 128 chars. diff --git a/appwrite/services/databases.py b/appwrite/services/databases.py index 51831aec..32c63923 100644 --- a/appwrite/services/databases.py +++ b/appwrite/services/databases.py @@ -642,7 +642,7 @@ def create_collection( enabled : Optional[bool] Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled. attributes : Optional[List[Dict[str, Any]]] - Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options. + Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, varchar, text, mediumtext, longtext, integer, bigint, double, boolean, datetime, point, linestring, polygon, email, url, ip, enum), size (integer, required for string and varchar types), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options. indexes : Optional[List[Dict[str, Any]]] Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC/DESC, optional), and lengths (array of integers, optional). @@ -2951,7 +2951,7 @@ def create_relationship_attribute( related_collection_id : str Related Collection ID. type : RelationshipType - Relation type + Relationship type. Possible values are: oneToOne, oneToMany, manyToOne, manyToMany. two_way : Optional[bool] Is Two Way? key : Optional[str] @@ -2959,7 +2959,7 @@ def create_relationship_attribute( two_way_key : Optional[str] Two Way Attribute Key. on_delete : Optional[RelationMutate] - Constraints option + Delete constraint. Possible values are: cascade, restrict, setNull. Returns ------- @@ -3033,7 +3033,7 @@ def update_relationship_attribute( key : str Attribute Key. on_delete : Optional[RelationMutate] - Constraints option + Delete constraint. Possible values are: cascade, restrict, setNull. new_key : Optional[str] New Attribute Key. diff --git a/appwrite/services/embeddings.py b/appwrite/services/embeddings.py new file mode 100644 index 00000000..50848d06 --- /dev/null +++ b/appwrite/services/embeddings.py @@ -0,0 +1,58 @@ +from ..service import Service +from urllib.parse import quote +from typing import Any, Dict, List, Optional, Union +from ..exception import AppwriteException +from appwrite.utils.deprecated import deprecated +from ..enums.embedding_model import EmbeddingModel +from ..models.embedding_list import EmbeddingList + +class Embeddings(Service): + + def __init__(self, client) -> None: + super(Embeddings, self).__init__(client) + + def create_text_embeddings( + self, + texts: List[str], + model: Optional[EmbeddingModel] = None + ) -> EmbeddingList: + """ + Generate vector embeddings for an array of text using the selected embedding model. Use the returned vectors to power semantic search and similarity queries against your vector collections. + + + Parameters + ---------- + texts : List[str] + Array of text to generate embeddings. + model : Optional[EmbeddingModel] + The embedding model to use for generating vector embeddings. + + Returns + ------- + EmbeddingList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/embeddings/text' + api_params = {} + if texts is None: + raise AppwriteException('Missing required parameter: "texts"') + + + api_params['texts'] = self._normalize_value(texts) + if model is not None: + api_params['model'] = self._normalize_value(model) + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=EmbeddingList) + diff --git a/appwrite/services/functions.py b/appwrite/services/functions.py index 4b0f552f..07bd41b4 100644 --- a/appwrite/services/functions.py +++ b/appwrite/services/functions.py @@ -260,7 +260,7 @@ def list_specifications( Parameters ---------- type : Optional[str] - Specification type to list. Can be one of: runtimes, builds. + Specification type to list. Can be one of: runtimes, builds. Defaults to runtimes. Returns ------- diff --git a/appwrite/services/project.py b/appwrite/services/project.py index 38af962f..1665fa81 100644 --- a/appwrite/services/project.py +++ b/appwrite/services/project.py @@ -7,8 +7,8 @@ from ..enums.project_auth_method_id import ProjectAuthMethodId from ..models.key_list import KeyList from ..enums.project_key_scopes import ProjectKeyScopes -from ..models.key import Key from ..models.ephemeral_key import EphemeralKey +from ..models.key import Key from ..models.mock_number_list import MockNumberList from ..models.mock_number import MockNumber from ..models.o_auth2_provider_list import OAuth2ProviderList @@ -74,6 +74,7 @@ from ..models.policy_session_limit import PolicySessionLimit from ..models.policy_user_limit import PolicyUserLimit from ..models.policy_membership_privacy import PolicyMembershipPrivacy +from ..models.policy_mfa_factors import PolicyMfaFactors from ..models.policy_deny_aliased_email import PolicyDenyAliasedEmail from ..models.policy_deny_disposable_email import PolicyDenyDisposableEmail from ..models.policy_deny_free_email import PolicyDenyFreeEmail @@ -237,67 +238,6 @@ def list_keys( return self._parse_response(response, model=KeyList) - def create_key( - self, - key_id: str, - name: str, - scopes: List[ProjectKeyScopes], - expire: Optional[str] = None - ) -> Key: - """ - Create a new API key. It's recommended to have multiple API keys with strict scopes for separate functions within your project. - - You can also create an ephemeral API key if you need a short-lived key instead. - - Parameters - ---------- - key_id : str - Key ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars. - name : str - Key name. Max length: 128 chars. - scopes : List[ProjectKeyScopes] - Key scopes list. Maximum of 200 scopes are allowed. - expire : Optional[str] - Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration. - - Returns - ------- - Key - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/project/keys' - api_params = {} - if key_id is None: - raise AppwriteException('Missing required parameter: "key_id"') - - if name is None: - raise AppwriteException('Missing required parameter: "name"') - - if scopes is None: - raise AppwriteException('Missing required parameter: "scopes"') - - - api_params['keyId'] = self._normalize_value(key_id) - api_params['name'] = self._normalize_value(name) - api_params['scopes'] = self._normalize_value(scopes) - if expire is not None: - api_params['expire'] = self._normalize_value(expire) - - response = self.client.call('post', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'content-type': 'application/json', - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=Key) - - def create_ephemeral_key( self, scopes: List[ProjectKeyScopes], @@ -796,7 +736,8 @@ def update_o_auth2_server( user_code_length: Optional[float] = None, user_code_format: Optional[str] = None, device_code_duration: Optional[float] = None, - default_scopes: Optional[List[str]] = None + default_scopes: Optional[List[str]] = None, + installation_scopes: Optional[List[str]] = None ) -> ProjectModel: """ Update the OAuth2 server (OIDC provider) configuration. @@ -833,6 +774,8 @@ def update_o_auth2_server( Lifetime in seconds of device flow device codes and user codes. Device codes are intentionally short-lived. Leave empty to use default 600. default_scopes : Optional[List[str]] List of OAuth2 scopes used when an authorization request omits the scope parameter. Every default scope must also be allowed by the OAuth2 server. Maximum of 100 scopes are allowed, each up to 128 characters long. + installation_scopes : Optional[List[str]] + List of scopes an application may request when installed on a team. Omitting the parameter clears the list, so no installation scopes can be granted. Maximum of 100 scopes are allowed, each up to 128 characters long. Returns ------- @@ -882,6 +825,8 @@ def update_o_auth2_server( api_params['deviceCodeDuration'] = self._normalize_value(device_code_duration) if default_scopes is not None: api_params['defaultScopes'] = self._normalize_value(default_scopes) + if installation_scopes is not None: + api_params['installationScopes'] = self._normalize_value(installation_scopes) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -4149,6 +4094,59 @@ def update_membership_privacy_policy( return self._parse_response(response, model=ProjectModel) + def update_mfa_factors_policy( + self, + totp: Optional[bool] = None, + email: Optional[bool] = None, + phone: Optional[bool] = None, + custom: Optional[bool] = None + ) -> ProjectModel: + """ + Updating this policy allows you to control which factors users can use to complete an MFA challenge. Disabled factors cannot be used to create a challenge and are reported as unavailable when listing factors. The custom factor is disabled by default; enable it to deliver challenge codes through your own channel. Recovery codes always remain available as a fallback. + + Parameters + ---------- + totp : Optional[bool] + Set to true to allow TOTP to complete an MFA challenge, or false to disable it. + email : Optional[bool] + Set to true to allow email to complete an MFA challenge, or false to disable it. + phone : Optional[bool] + Set to true to allow phone (SMS) to complete an MFA challenge, or false to disable it. + custom : Optional[bool] + Set to true to allow the custom factor to complete an MFA challenge, or false to disable it. + + Returns + ------- + ProjectModel + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/project/policies/mfa-factors' + api_params = {} + + if totp is not None: + api_params['totp'] = self._normalize_value(totp) + if email is not None: + api_params['email'] = self._normalize_value(email) + if phone is not None: + api_params['phone'] = self._normalize_value(phone) + if custom is not None: + api_params['custom'] = self._normalize_value(custom) + + response = self.client.call('patch', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=ProjectModel) + + def update_password_dictionary_policy( self, enabled: bool @@ -4201,7 +4199,7 @@ def update_password_history_policy( Parameters ---------- total : Optional[float] - Set the password history length per user. Value can be between 1 and 5000, or null to disable the limit. + Set the password history length per user. Value can be between 1 and 20, or null to disable the limit. Returns ------- @@ -4376,7 +4374,7 @@ def update_session_duration_policy( Parameters ---------- duration : float - Maximum session length in seconds. Minium allowed value is 5 second, and maximum is 1 year, which is 31536000 seconds. + Maximum session length in seconds. Minium allowed value is 60 seconds, and maximum is 1 year, which is 31536000 seconds. Returns ------- @@ -4448,15 +4446,15 @@ def update_session_invalidation_policy( def update_session_limit_policy( self, - total: Optional[float] + total: float ) -> ProjectModel: """ Update the maximum number of sessions allowed per user. When the limit is hit, the oldest session will be deleted to make room for new one. Parameters ---------- - total : Optional[float] - Set the maximum number of sessions allowed per user. Value can be between 1 and 5000, or null to disable the limit. + total : float + Set the maximum number of sessions allowed per user. Value can be between 1 and 100. Returns ------- @@ -4471,6 +4469,9 @@ def update_session_limit_policy( api_path = '/project/policies/session-limit' api_params = {} + if total is None: + raise AppwriteException('Missing required parameter: "total"') + api_params['total'] = self._normalize_value(total) @@ -4493,7 +4494,7 @@ def update_user_limit_policy( Parameters ---------- total : Optional[float] - Set the maximum number of users allowed in the project. Value can be between 1 and 5000, or null to disable the limit. + Set the maximum number of users allowed in the project. Value can be between 0 and 10000. Use 0 or null to disable the limit. Returns ------- @@ -4523,18 +4524,18 @@ def update_user_limit_policy( def get_policy( self, policy_id: ProjectPolicyId - ) -> Union[PolicyPasswordDictionary, PolicyPasswordHistory, PolicyPasswordStrength, PolicyPasswordPersonalData, PolicySessionAlert, PolicySessionDuration, PolicySessionInvalidation, PolicySessionLimit, PolicyUserLimit, PolicyMembershipPrivacy, PolicyDenyAliasedEmail, PolicyDenyDisposableEmail, PolicyDenyFreeEmail, PolicyDenyCorporateEmail]: + ) -> Union[PolicyPasswordDictionary, PolicyPasswordHistory, PolicyPasswordStrength, PolicyPasswordPersonalData, PolicySessionAlert, PolicySessionDuration, PolicySessionInvalidation, PolicySessionLimit, PolicyUserLimit, PolicyMembershipPrivacy, PolicyMfaFactors, PolicyDenyAliasedEmail, PolicyDenyDisposableEmail, PolicyDenyFreeEmail, PolicyDenyCorporateEmail]: """ Get a policy by its unique ID. This endpoint returns the current configuration for the requested project policy. Parameters ---------- policy_id : ProjectPolicyId - Policy ID. Can be one of: password-dictionary, password-history, password-strength, password-personal-data, session-alert, session-duration, session-invalidation, session-limit, user-limit, membership-privacy, deny-aliased-email, deny-disposable-email, deny-free-email, deny-corporate-email. + Policy ID. Can be one of: password-dictionary, password-history, password-strength, password-personal-data, session-alert, session-duration, session-invalidation, session-limit, user-limit, membership-privacy, mfa-factors, deny-aliased-email, deny-disposable-email, deny-free-email, deny-corporate-email. Returns ------- - Union[PolicyPasswordDictionary, PolicyPasswordHistory, PolicyPasswordStrength, PolicyPasswordPersonalData, PolicySessionAlert, PolicySessionDuration, PolicySessionInvalidation, PolicySessionLimit, PolicyUserLimit, PolicyMembershipPrivacy, PolicyDenyAliasedEmail, PolicyDenyDisposableEmail, PolicyDenyFreeEmail, PolicyDenyCorporateEmail] + Union[PolicyPasswordDictionary, PolicyPasswordHistory, PolicyPasswordStrength, PolicyPasswordPersonalData, PolicySessionAlert, PolicySessionDuration, PolicySessionInvalidation, PolicySessionLimit, PolicyUserLimit, PolicyMembershipPrivacy, PolicyMfaFactors, PolicyDenyAliasedEmail, PolicyDenyDisposableEmail, PolicyDenyFreeEmail, PolicyDenyCorporateEmail] API response as one of the typed response models Raises @@ -4588,6 +4589,9 @@ def get_policy( if response.get('$id') == 'membership-privacy': return self._parse_response(response, model=PolicyMembershipPrivacy) + if response.get('$id') == 'mfa-factors': + return self._parse_response(response, model=PolicyMfaFactors) + if response.get('$id') == 'deny-aliased-email': return self._parse_response(response, model=PolicyDenyAliasedEmail) diff --git a/appwrite/services/proxy.py b/appwrite/services/proxy.py index 44f18747..ce209139 100644 --- a/appwrite/services/proxy.py +++ b/appwrite/services/proxy.py @@ -3,6 +3,8 @@ from typing import Any, Dict, List, Optional, Union from ..exception import AppwriteException from appwrite.utils.deprecated import deprecated +from ..enums.invalidation_type import InvalidationType +from ..models.proxy_invalidation import ProxyInvalidation from ..models.proxy_rule_list import ProxyRuleList from ..models.proxy_rule import ProxyRule from ..enums.status_code import StatusCode @@ -13,6 +15,60 @@ class Proxy(Service): def __init__(self, client) -> None: super(Proxy, self).__init__(client) + def create_invalidation( + self, + domain: str, + type: InvalidationType, + reference: Optional[str] = None + ) -> ProxyInvalidation: + """ + Create a new CDN cache invalidation for a domain. Executes a hard purge of cached content. + + Depending on type, the invalidation purges a single cache tag, a single URL path, or all cached content for the domain. + + Parameters + ---------- + domain : str + Domain name. + type : InvalidationType + Type of reference passed. Allowed values are: tag, path, all + reference : Optional[str] + Reference to invalidate. Depending on type this can be: cache tag name (up to 128 characters), URL path (up to 2048 characters). Not required when type is all. + + Returns + ------- + ProxyInvalidation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/proxy/invalidations' + api_params = {} + if domain is None: + raise AppwriteException('Missing required parameter: "domain"') + + if type is None: + raise AppwriteException('Missing required parameter: "type"') + + + api_params['domain'] = self._normalize_value(domain) + api_params['type'] = self._normalize_value(type) + if reference is not None: + api_params['reference'] = self._normalize_value(reference) + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=ProxyInvalidation) + + def list_rules( self, queries: Optional[List[str]] = None, diff --git a/appwrite/services/sites.py b/appwrite/services/sites.py index bcf0c1ad..8b930f17 100644 --- a/appwrite/services/sites.py +++ b/appwrite/services/sites.py @@ -267,7 +267,7 @@ def list_specifications( Parameters ---------- type : Optional[str] - Specification type to list. Can be one of: runtimes, builds. + Specification type to list. Can be one of: runtimes, builds. Defaults to runtimes. Returns ------- diff --git a/appwrite/services/storage.py b/appwrite/services/storage.py index a450e58b..7511db08 100644 --- a/appwrite/services/storage.py +++ b/appwrite/services/storage.py @@ -341,7 +341,7 @@ def list_files( bucket_id : str Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket). queries : Optional[List[str]] - Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, signature, mimeType, sizeOriginal, chunksTotal, chunksUploaded + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, folder, signature, mimeType, sizeOriginal, chunksTotal, chunksUploaded search : Optional[str] Search term to filter your list results. Max length: 256 chars. total : Optional[bool] @@ -386,6 +386,7 @@ def create_file( file_id: str, file: InputFile, permissions: Optional[List[str]] = None, + folder: Optional[str] = None, on_progress = None ) -> File: """ @@ -408,6 +409,8 @@ def create_file( Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/products/storage/upload-download#input-file). permissions : Optional[List[str]] An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). + folder : Optional[str] + Virtual folder to place the file in, for example "photos/2026". Nest folders with `/`. Defaults to the bucket root. on_progress : callable, optional Optional callback for upload progress @@ -439,6 +442,8 @@ def create_file( api_params['file'] = self._normalize_value(file) if permissions is not None: api_params['permissions'] = self._normalize_value(permissions) + if folder is not None: + api_params['folder'] = self._normalize_value(folder) param_name = 'file' diff --git a/appwrite/services/tables_db.py b/appwrite/services/tables_db.py index f84f9cca..94854111 100644 --- a/appwrite/services/tables_db.py +++ b/appwrite/services/tables_db.py @@ -9,6 +9,9 @@ from ..models.transaction_list import TransactionList from ..models.transaction import Transaction from ..models.dedicated_database import DedicatedDatabase +from ..models.database_migration_list import DatabaseMigrationList +from ..models.database_migration import DatabaseMigration +from ..models.dedicated_database_operation_list import DedicatedDatabaseOperationList from ..models.dedicated_database_replicas import DedicatedDatabaseReplicas from ..models.database_status import DatabaseStatus from ..models.table_list import TableList @@ -101,7 +104,8 @@ def create( name: str, enabled: Optional[bool] = None, specification: Optional[str] = None, - replicas: Optional[float] = None + replicas: Optional[float] = None, + sync_mode: Optional[str] = None ) -> Database: """ Create a new Database. @@ -119,6 +123,8 @@ def create( Database specification. Defaults to `serverless`, which creates the database on the shared pool. Any other value provisions a dedicated database on that specification. replicas : Optional[float] Number of high availability replicas (0-5) for the dedicated database backing this database. Requires a dedicated `specification`; must be 0 for a serverless database. High availability is enabled when greater than 0. + sync_mode : Optional[str] + Replication sync mode for the dedicated database backing this database. Requires a dedicated `specification`; the mode is only in force once there is at least one replica. Allowed values: async, sync, quorum. Returns ------- @@ -148,6 +154,8 @@ def create( api_params['specification'] = self._normalize_value(specification) if replicas is not None: api_params['replicas'] = self._normalize_value(replicas) + if sync_mode is not None: + api_params['syncMode'] = self._normalize_value(sync_mode) response = self.client.call('post', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -478,7 +486,9 @@ def update( database_id: str, name: Optional[str] = None, enabled: Optional[bool] = None, - replicas: Optional[float] = None + specification: Optional[str] = None, + replicas: Optional[float] = None, + sync_mode: Optional[str] = None ) -> Database: """ Update a database by its unique ID. @@ -491,8 +501,12 @@ def update( Database name. Max length: 128 chars. enabled : Optional[bool] Is database enabled? When set to 'disabled', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled. + specification : Optional[str] + Database specification. Resizing between dedicated specifications changes cpu, memory, storage and the connection ceiling via a rolling cutover with zero downtime. Moving a `serverless` database onto a dedicated specification is a data migration, not a resize. replicas : Optional[float] Number of high availability replicas (0-5) for the dedicated database backing this database. Only valid when the database is backed by a dedicated specification. High availability is enabled when greater than 0. + sync_mode : Optional[str] + Replication sync mode for the dedicated database backing this database. Only valid when the database is backed by a dedicated specification; the mode is only in force once there is at least one replica. Allowed values: async, sync, quorum. Returns ------- @@ -516,8 +530,12 @@ def update( api_params['name'] = self._normalize_value(name) if enabled is not None: api_params['enabled'] = self._normalize_value(enabled) + if specification is not None: + api_params['specification'] = self._normalize_value(specification) if replicas is not None: api_params['replicas'] = self._normalize_value(replicas) + if sync_mode is not None: + api_params['syncMode'] = self._normalize_value(sync_mode) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -573,7 +591,7 @@ def create_failover( target_replica_id: Optional[str] = None ) -> DedicatedDatabase: """ - Trigger a manual failover for a dedicated database with high availability enabled. Promotes a replica to primary. The failover runs asynchronously; poll the database document for status updates. + Trigger a manual failover for a dedicated database with high availability enabled. Promotes a replica to primary. The failover runs asynchronously; poll the database document for status updates. A database left mid-operation by a failover that did not finish also accepts this call as a repair, provided `targetReplicaId` names the member to promote. Parameters ---------- @@ -612,6 +630,291 @@ def create_failover( return self._parse_response(response, model=DedicatedDatabase) + def list_migrations( + self, + database_id: str + ) -> DatabaseMigrationList: + """ + List the dedicated migrations for a TablesDB database. A database has at most one in-flight migration. + + Parameters + ---------- + database_id : str + Database ID. + + Returns + ------- + DatabaseMigrationList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/tablesdb/{databaseId}/migrations' + api_params = {} + if database_id is None: + raise AppwriteException('Missing required parameter: "database_id"') + + api_path = api_path.replace('{databaseId}', str(self._normalize_value(database_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=DatabaseMigrationList) + + + def create_migration( + self, + database_id: str, + specification: str, + auto_cutover: Optional[bool] = None + ) -> DatabaseMigration: + """ + Start migrating a serverless TablesDB database onto a dedicated MySQL compute. Data is copied to the target while the source stays live, with a brief read-only window during cutover. + + Parameters + ---------- + database_id : str + Database ID. + specification : str + Dedicated compute specification to provision as the migration target (e.g. s-2vcpu-4gb). The migration always targets a dedicated compute, so `serverless` is not accepted. + auto_cutover : Optional[bool] + Whether to cut over automatically once the copy is verified. When disabled the migration parks at ready_to_cutover and holds there until the cutover is performed manually. + + Returns + ------- + DatabaseMigration + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/tablesdb/{databaseId}/migrations' + api_params = {} + if database_id is None: + raise AppwriteException('Missing required parameter: "database_id"') + + if specification is None: + raise AppwriteException('Missing required parameter: "specification"') + + api_path = api_path.replace('{databaseId}', str(self._normalize_value(database_id))) + + api_params['specification'] = self._normalize_value(specification) + if auto_cutover is not None: + api_params['autoCutover'] = self._normalize_value(auto_cutover) + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=DatabaseMigration) + + + def get_migration( + self, + database_id: str, + migration_id: str + ) -> DatabaseMigration: + """ + Get a single dedicated migration for a TablesDB database by its ID. + + Parameters + ---------- + database_id : str + Database ID. + migration_id : str + Migration ID. + + Returns + ------- + DatabaseMigration + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/tablesdb/{databaseId}/migrations/{migrationId}' + api_params = {} + if database_id is None: + raise AppwriteException('Missing required parameter: "database_id"') + + if migration_id is None: + raise AppwriteException('Missing required parameter: "migration_id"') + + api_path = api_path.replace('{databaseId}', str(self._normalize_value(database_id))) + api_path = api_path.replace('{migrationId}', str(self._normalize_value(migration_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=DatabaseMigration) + + + def delete_migration( + self, + database_id: str, + migration_id: str + ) -> Dict[str, Any]: + """ + Abort an in-flight TablesDB dedicated migration. Only allowed before cutover; once the migration has cut over it cannot be aborted. + + Parameters + ---------- + database_id : str + Database ID. + migration_id : str + Migration ID. + + Returns + ------- + Dict[str, Any] + API response as a dictionary + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/tablesdb/{databaseId}/migrations/{migrationId}' + api_params = {} + if database_id is None: + raise AppwriteException('Missing required parameter: "database_id"') + + if migration_id is None: + raise AppwriteException('Missing required parameter: "migration_id"') + + api_path = api_path.replace('{databaseId}', str(self._normalize_value(database_id))) + api_path = api_path.replace('{migrationId}', str(self._normalize_value(migration_id))) + + + response = self.client.call('delete', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return response + + + def cutover_migration( + self, + database_id: str, + migration_id: str + ) -> DatabaseMigration: + """ + Cut a verified TablesDB migration over to its dedicated compute. Only applies to a migration created with `autoCutover` disabled, which waits at `ready_to_cutover` until this is called. The routing flip happens shortly after this returns, with a brief read-only window. One call buys one attempt: a cutover that fails a check returns the migration to `verifying` and parks it again, so call this once more to retry. + + Parameters + ---------- + database_id : str + Database ID. + migration_id : str + Migration ID. + + Returns + ------- + DatabaseMigration + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/tablesdb/{databaseId}/migrations/{migrationId}/cutover' + api_params = {} + if database_id is None: + raise AppwriteException('Missing required parameter: "database_id"') + + if migration_id is None: + raise AppwriteException('Missing required parameter: "migration_id"') + + api_path = api_path.replace('{databaseId}', str(self._normalize_value(database_id))) + api_path = api_path.replace('{migrationId}', str(self._normalize_value(migration_id))) + + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=DatabaseMigration) + + + def list_operations( + self, + database_id: str, + status: Optional[str] = None, + limit: Optional[float] = None, + offset: Optional[float] = None + ) -> DedicatedDatabaseOperationList: + """ + List the lifecycle operations recorded for a dedicated database, newest first. Every provision, update, restore, backup and replication action is recorded here with its outcome, including an attempt that was abandoned because another worker took over the database. + + Parameters + ---------- + database_id : str + Database ID. + status : Optional[str] + Filter by operation status. + limit : Optional[float] + Maximum number of operations to return. + offset : Optional[float] + Number of operations to skip. + + Returns + ------- + DedicatedDatabaseOperationList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/tablesdb/{databaseId}/operations' + api_params = {} + if database_id is None: + raise AppwriteException('Missing required parameter: "database_id"') + + api_path = api_path.replace('{databaseId}', str(self._normalize_value(database_id))) + + if status is not None: + api_params['status'] = self._normalize_value(status) + if limit is not None: + api_params['limit'] = self._normalize_value(limit) + if offset is not None: + api_params['offset'] = self._normalize_value(offset) + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=DedicatedDatabaseOperationList) + + def get_replicas( self, database_id: str @@ -773,7 +1076,7 @@ def create_table( enabled : Optional[bool] Is table enabled? When set to 'disabled', users cannot access the table but Server SDKs with and API key can still read and write to the table. No data is lost when this is toggled. columns : Optional[List[Dict[str, Any]]] - Array of column definitions to create. Each column should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options. + Array of column definitions to create. Each column should contain: key (string), type (string: string, varchar, text, mediumtext, longtext, integer, bigint, double, boolean, datetime, point, linestring, polygon, email, url, ip, enum), size (integer, required for string and varchar types), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options. indexes : Optional[List[Dict[str, Any]]] Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of column keys), orders (array of ASC/DESC, optional), and lengths (array of integers, optional). @@ -2988,7 +3291,7 @@ def create_relationship_column( related_table_id : str Related Table ID. type : RelationshipType - Relation type + Relationship type. Possible values are: oneToOne, oneToMany, manyToOne, manyToMany. two_way : Optional[bool] Is Two Way? key : Optional[str] @@ -2996,7 +3299,7 @@ def create_relationship_column( two_way_key : Optional[str] Two Way Column Key. on_delete : Optional[RelationMutate] - Constraints option + Delete constraint. Possible values are: cascade, restrict, setNull. Returns ------- @@ -3822,7 +4125,7 @@ def update_relationship_column( key : str Column Key. on_delete : Optional[RelationMutate] - Constraints option + Delete constraint. Possible values are: cascade, restrict, setNull. new_key : Optional[str] New Column Key. diff --git a/appwrite/services/users.py b/appwrite/services/users.py index 8eab2dc7..fad7bd3c 100644 --- a/appwrite/services/users.py +++ b/appwrite/services/users.py @@ -11,6 +11,7 @@ from ..models.log_list import LogList from ..models.membership_list import MembershipList from ..enums.authenticator_type import AuthenticatorType +from ..models.mfa_challenge_secret import MfaChallengeSecret from ..models.mfa_factors import MfaFactors from ..models.mfa_recovery_codes import MfaRecoveryCodes from ..models.preferences import Preferences @@ -1219,6 +1220,52 @@ def delete_mfa_authenticator( return response + def get_mfa_challenge( + self, + user_id: str, + challenge_id: str + ) -> MfaChallengeSecret: + """ + Get a custom MFA challenge for a user, including the code to be delivered through your own channel. + + Parameters + ---------- + user_id : str + User ID. + challenge_id : str + ID of the challenge. + + Returns + ------- + MfaChallengeSecret + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/users/{userId}/mfa/challenges/{challengeId}' + api_params = {} + if user_id is None: + raise AppwriteException('Missing required parameter: "user_id"') + + if challenge_id is None: + raise AppwriteException('Missing required parameter: "challenge_id"') + + api_path = api_path.replace('{userId}', str(self._normalize_value(user_id))) + api_path = api_path.replace('{challengeId}', str(self._normalize_value(challenge_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=MfaChallengeSecret) + + def list_mfa_factors( self, user_id: str diff --git a/docs/examples/activities/list-events.md b/docs/examples/activities/list-events.md index bbeb644b..17f3b67d 100644 --- a/docs/examples/activities/list-events.md +++ b/docs/examples/activities/list-events.md @@ -11,7 +11,7 @@ client.set_key('') # Your secret API key activities = Activities(client) result: ActivityEventList = activities.list_events( - queries = '' # optional + queries = [] # optional ) print(result.model_dump()) diff --git a/docs/examples/apps/create-installation-token.md b/docs/examples/apps/create-installation-token.md index 62a62ca6..acbe6cc7 100644 --- a/docs/examples/apps/create-installation-token.md +++ b/docs/examples/apps/create-installation-token.md @@ -6,7 +6,7 @@ from appwrite.models import Oauth2Token client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID -client.set_key('') # Your secret API key +client.set_session('') # The user session to authenticate with apps = Apps(client) diff --git a/docs/examples/account/create-jwt.md b/docs/examples/apps/delete-installation.md similarity index 58% rename from docs/examples/account/create-jwt.md rename to docs/examples/apps/delete-installation.md index a28c65fd..2a941e8e 100644 --- a/docs/examples/account/create-jwt.md +++ b/docs/examples/apps/delete-installation.md @@ -1,18 +1,16 @@ ```python from appwrite.client import Client -from appwrite.services.account import Account -from appwrite.models import Jwt +from appwrite.services.apps import Apps client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID client.set_session('') # The user session to authenticate with -account = Account(client) +apps = Apps(client) -result: Jwt = account.create_jwt( - duration = 0 # optional +result = apps.delete_installation( + app_id = '', + installation_id = '' ) - -print(result.model_dump()) ``` diff --git a/docs/examples/apps/get-installation.md b/docs/examples/apps/get-installation.md index f6a52721..d71858c9 100644 --- a/docs/examples/apps/get-installation.md +++ b/docs/examples/apps/get-installation.md @@ -6,7 +6,7 @@ from appwrite.models import AppInstallation client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID -client.set_key('') # Your secret API key +client.set_session('') # The user session to authenticate with apps = Apps(client) diff --git a/docs/examples/apps/list-installations.md b/docs/examples/apps/list-installations.md index f5e292b2..e21575cb 100644 --- a/docs/examples/apps/list-installations.md +++ b/docs/examples/apps/list-installations.md @@ -6,7 +6,7 @@ from appwrite.models import AppInstallationList client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint client.set_project('') # Your project ID -client.set_key('') # Your secret API key +client.set_session('') # The user session to authenticate with apps = Apps(client) diff --git a/docs/examples/embeddings/create-text-embeddings.md b/docs/examples/embeddings/create-text-embeddings.md new file mode 100644 index 00000000..49d3c258 --- /dev/null +++ b/docs/examples/embeddings/create-text-embeddings.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.embeddings import Embeddings +from appwrite.models import EmbeddingList +from appwrite.enums import EmbeddingModel + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +embeddings = Embeddings(client) + +result: EmbeddingList = embeddings.create_text_embeddings( + texts = [], + model = EmbeddingModel.NOMIC_EMBED_TEXT # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/project/get-policy.md b/docs/examples/project/get-policy.md index f9487669..16caef14 100644 --- a/docs/examples/project/get-policy.md +++ b/docs/examples/project/get-policy.md @@ -11,6 +11,7 @@ from appwrite.models import PolicySessionInvalidation from appwrite.models import PolicySessionLimit from appwrite.models import PolicyUserLimit from appwrite.models import PolicyMembershipPrivacy +from appwrite.models import PolicyMfaFactors from appwrite.models import PolicyDenyAliasedEmail from appwrite.models import PolicyDenyDisposableEmail from appwrite.models import PolicyDenyFreeEmail @@ -25,7 +26,7 @@ client.set_key('') # Your secret API key project = Project(client) -result: Union[PolicyPasswordDictionary, PolicyPasswordHistory, PolicyPasswordStrength, PolicyPasswordPersonalData, PolicySessionAlert, PolicySessionDuration, PolicySessionInvalidation, PolicySessionLimit, PolicyUserLimit, PolicyMembershipPrivacy, PolicyDenyAliasedEmail, PolicyDenyDisposableEmail, PolicyDenyFreeEmail, PolicyDenyCorporateEmail] = project.get_policy( +result: Union[PolicyPasswordDictionary, PolicyPasswordHistory, PolicyPasswordStrength, PolicyPasswordPersonalData, PolicySessionAlert, PolicySessionDuration, PolicySessionInvalidation, PolicySessionLimit, PolicyUserLimit, PolicyMembershipPrivacy, PolicyMfaFactors, PolicyDenyAliasedEmail, PolicyDenyDisposableEmail, PolicyDenyFreeEmail, PolicyDenyCorporateEmail] = project.get_policy( policy_id = ProjectPolicyId.PASSWORD_DICTIONARY ) diff --git a/docs/examples/project/create-key.md b/docs/examples/project/update-mfa-factors-policy.md similarity index 58% rename from docs/examples/project/create-key.md rename to docs/examples/project/update-mfa-factors-policy.md index a3e040f7..67f75688 100644 --- a/docs/examples/project/create-key.md +++ b/docs/examples/project/update-mfa-factors-policy.md @@ -1,8 +1,7 @@ ```python from appwrite.client import Client from appwrite.services.project import Project -from appwrite.models import Key -from appwrite.enums import ProjectKeyScopes +from appwrite.models import Project as ProjectModel client = Client() client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint @@ -11,11 +10,11 @@ client.set_key('') # Your secret API key project = Project(client) -result: Key = project.create_key( - key_id = '', - name = '', - scopes = [ProjectKeyScopes.PROJECT_READ], - expire = '2020-10-15T06:38:00.000+00:00' # optional +result: ProjectModel = project.update_mfa_factors_policy( + totp = False, # optional + email = False, # optional + phone = False, # optional + custom = False # optional ) print(result.model_dump()) diff --git a/docs/examples/project/update-o-auth-2-server.md b/docs/examples/project/update-o-auth-2-server.md index bce0876e..338ebf33 100644 --- a/docs/examples/project/update-o-auth-2-server.md +++ b/docs/examples/project/update-o-auth-2-server.md @@ -25,7 +25,8 @@ result: ProjectModel = project.update_o_auth2_server( user_code_length = 6, # optional user_code_format = 'numeric', # optional device_code_duration = 60, # optional - default_scopes = [] # optional + default_scopes = [], # optional + installation_scopes = [] # optional ) print(result.model_dump()) diff --git a/docs/examples/project/update-session-duration-policy.md b/docs/examples/project/update-session-duration-policy.md index 768bd4ff..817947e4 100644 --- a/docs/examples/project/update-session-duration-policy.md +++ b/docs/examples/project/update-session-duration-policy.md @@ -11,7 +11,7 @@ client.set_key('') # Your secret API key project = Project(client) result: ProjectModel = project.update_session_duration_policy( - duration = 5 + duration = 60 ) print(result.model_dump()) diff --git a/docs/examples/project/update-user-limit-policy.md b/docs/examples/project/update-user-limit-policy.md index e841acae..42d66de3 100644 --- a/docs/examples/project/update-user-limit-policy.md +++ b/docs/examples/project/update-user-limit-policy.md @@ -11,7 +11,7 @@ client.set_key('') # Your secret API key project = Project(client) result: ProjectModel = project.update_user_limit_policy( - total = 1 + total = 0 ) print(result.model_dump()) diff --git a/docs/examples/proxy/create-invalidation.md b/docs/examples/proxy/create-invalidation.md new file mode 100644 index 00000000..f94b4168 --- /dev/null +++ b/docs/examples/proxy/create-invalidation.md @@ -0,0 +1,21 @@ +```python +from appwrite.client import Client +from appwrite.services.proxy import Proxy +from appwrite.models import ProxyInvalidation +from appwrite.enums import InvalidationType + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +proxy = Proxy(client) + +result: ProxyInvalidation = proxy.create_invalidation( + domain = '', + type = InvalidationType.TAG, + reference = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/storage/create-file.md b/docs/examples/storage/create-file.md index 2103a1d6..cfec75c1 100644 --- a/docs/examples/storage/create-file.md +++ b/docs/examples/storage/create-file.md @@ -17,7 +17,8 @@ result: File = storage.create_file( bucket_id = '', file_id = '', file = InputFile.from_path('file.png'), - permissions = [Permission.read(Role.any())] # optional + permissions = [Permission.read(Role.any())], # optional + folder = '' # optional ) print(result.model_dump()) diff --git a/docs/examples/tablesdb/create-migration.md b/docs/examples/tablesdb/create-migration.md new file mode 100644 index 00000000..46dd29bc --- /dev/null +++ b/docs/examples/tablesdb/create-migration.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import DatabaseMigration + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result: DatabaseMigration = tables_db.create_migration( + database_id = '', + specification = 's-1vcpu-1gb', + auto_cutover = False # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/tablesdb/create.md b/docs/examples/tablesdb/create.md index 0c5260cf..ca668875 100644 --- a/docs/examples/tablesdb/create.md +++ b/docs/examples/tablesdb/create.md @@ -15,7 +15,8 @@ result: Database = tables_db.create( name = '', enabled = False, # optional specification = 'serverless', # optional - replicas = 0 # optional + replicas = 0, # optional + sync_mode = 'async' # optional ) print(result.model_dump()) diff --git a/docs/examples/tablesdb/cutover-migration.md b/docs/examples/tablesdb/cutover-migration.md new file mode 100644 index 00000000..1b793d63 --- /dev/null +++ b/docs/examples/tablesdb/cutover-migration.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import DatabaseMigration + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result: DatabaseMigration = tables_db.cutover_migration( + database_id = '', + migration_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/tablesdb/delete-migration.md b/docs/examples/tablesdb/delete-migration.md new file mode 100644 index 00000000..9442ae34 --- /dev/null +++ b/docs/examples/tablesdb/delete-migration.md @@ -0,0 +1,16 @@ +```python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result = tables_db.delete_migration( + database_id = '', + migration_id = '' +) +``` diff --git a/docs/examples/tablesdb/get-migration.md b/docs/examples/tablesdb/get-migration.md new file mode 100644 index 00000000..2efe0b63 --- /dev/null +++ b/docs/examples/tablesdb/get-migration.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import DatabaseMigration + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result: DatabaseMigration = tables_db.get_migration( + database_id = '', + migration_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/tablesdb/list-migrations.md b/docs/examples/tablesdb/list-migrations.md new file mode 100644 index 00000000..99665efa --- /dev/null +++ b/docs/examples/tablesdb/list-migrations.md @@ -0,0 +1,18 @@ +```python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import DatabaseMigrationList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result: DatabaseMigrationList = tables_db.list_migrations( + database_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/tablesdb/list-operations.md b/docs/examples/tablesdb/list-operations.md new file mode 100644 index 00000000..afe14338 --- /dev/null +++ b/docs/examples/tablesdb/list-operations.md @@ -0,0 +1,21 @@ +```python +from appwrite.client import Client +from appwrite.services.tables_db import TablesDB +from appwrite.models import DedicatedDatabaseOperationList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +tables_db = TablesDB(client) + +result: DedicatedDatabaseOperationList = tables_db.list_operations( + database_id = '', + status = 'running', # optional + limit = 1, # optional + offset = 0 # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/tablesdb/update.md b/docs/examples/tablesdb/update.md index 8609072e..7fc0c930 100644 --- a/docs/examples/tablesdb/update.md +++ b/docs/examples/tablesdb/update.md @@ -14,7 +14,9 @@ result: Database = tables_db.update( database_id = '', name = '', # optional enabled = False, # optional - replicas = 0 # optional + specification = 'serverless', # optional + replicas = 0, # optional + sync_mode = 'async' # optional ) print(result.model_dump()) diff --git a/docs/examples/users/get-mfa-challenge.md b/docs/examples/users/get-mfa-challenge.md new file mode 100644 index 00000000..8dde75a7 --- /dev/null +++ b/docs/examples/users/get-mfa-challenge.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.users import Users +from appwrite.models import MfaChallengeSecret + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +users = Users(client) + +result: MfaChallengeSecret = users.get_mfa_challenge( + user_id = '', + challenge_id = '' +) + +print(result.model_dump()) +``` diff --git a/pyproject.toml b/pyproject.toml index b71d5337..f01bffcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "appwrite" -version = "22.2.0" +version = "23.0.0" description = "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API" readme = "README.md" requires-python = ">=3.9" diff --git a/setup.py b/setup.py index 3d51ebff..980aaf26 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name = 'appwrite', packages = setuptools.find_packages(), - version = '22.2.0', + version = '23.0.0', license='BSD-3-Clause', description = 'Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API', long_description = long_description, @@ -18,7 +18,7 @@ maintainer = 'Appwrite Team', maintainer_email = 'team@appwrite.io', url = 'https://appwrite.io/support', - download_url='https://github.com/appwrite/sdk-for-python/archive/22.2.0.tar.gz', + download_url='https://github.com/appwrite/sdk-for-python/archive/23.0.0.tar.gz', install_requires=[ 'requests', 'pydantic>=2,<3', diff --git a/test/services/test_account.py b/test/services/test_account.py index c0bab652..648aba16 100644 --- a/test/services/test_account.py +++ b/test/services/test_account.py @@ -230,19 +230,6 @@ def test_delete_identity(self, m): self.assertEqual(response, data) - @requests_mock.Mocker() - def test_create_jwt(self, m): - data = { - "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.account.create_jwt( - ) - - self.assertEqual(response.to_dict(), data) - @requests_mock.Mocker() def test_list_logs(self, m): data = { @@ -526,7 +513,8 @@ def test_list_mfa_factors(self, m): "totp": True, "phone": True, "email": True, - "recoveryCode": True + "recoveryCode": True, + "custom": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -542,7 +530,8 @@ def test_list_mfa_factors(self, m): "totp": True, "phone": True, "email": True, - "recoveryCode": True + "recoveryCode": True, + "custom": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -727,7 +716,6 @@ def test_get_prefs(self, m): response = self.account.get_prefs( ) - data['data'] = {} self.assertEqual(response.to_dict(), data) @requests_mock.Mocker() diff --git a/test/services/test_apps.py b/test/services/test_apps.py index e923f2e5..8f9145a3 100644 --- a/test/services/test_apps.py +++ b/test/services/test_apps.py @@ -225,6 +225,19 @@ def test_get_installation(self, m): self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() + def test_delete_installation(self, m): + data = '' + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.delete_installation( + '', + '', + ) + + self.assertEqual(response, data) + @requests_mock.Mocker() def test_create_installation_token(self, m): data = { diff --git a/test/services/test_embeddings.py b/test/services/test_embeddings.py new file mode 100644 index 00000000..8d2cf848 --- /dev/null +++ b/test/services/test_embeddings.py @@ -0,0 +1,30 @@ +import json +import requests_mock +import unittest + +from appwrite.client import Client +from appwrite.input_file import InputFile +from appwrite.models import * +from appwrite.services.embeddings import Embeddings + +class EmbeddingsServiceTest(unittest.TestCase): + + def setUp(self): + self.client = Client() + self.embeddings = Embeddings(self.client) + + @requests_mock.Mocker() + def test_create_text_embeddings(self, m): + data = { + "total": 5.0, + "embeddings": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.embeddings.create_text_embeddings( + [], + ) + + self.assertEqual(response.to_dict(), data) + diff --git a/test/services/test_organization.py b/test/services/test_organization.py index 544d647d..2bf4dbc8 100644 --- a/test/services/test_organization.py +++ b/test/services/test_organization.py @@ -22,7 +22,6 @@ def test_get(self, m): "name": "VIP", "total": 7.0, "prefs": {}, - "billingBudget": 50.0, "budgetAlerts": [], "billingPlan": "tier-1", "billingPlanId": "tier-1", @@ -37,7 +36,6 @@ def test_get(self, m): "storage": 25.0, "imageTransformations": 100.0, "screenshotsGenerated": 50.0, - "members": 25.0, "webhooks": 25.0, "wafRules": 2.0, "projects": 2.0, @@ -61,7 +59,6 @@ def test_get(self, m): "topics": 1.0, "authPhone": 10.0, "domains": 5.0, - "activityLogs": 7.0, "usageLogs": 30.0, "projectInactivityDays": 7.0, "alertLimit": 80.0, @@ -82,14 +79,6 @@ def test_get(self, m): "value": 25.0, "invoiceDesc": "" }, - "member": { - "name": "", - "unit": "GB", - "currency": "USD", - "price": 5, - "value": 25.0, - "invoiceDesc": "" - }, "realtime": { "name": "", "unit": "GB", @@ -106,14 +95,6 @@ def test_get(self, m): "value": 25.0, "invoiceDesc": "" }, - "realtimeBandwidth": { - "name": "", - "unit": "GB", - "currency": "USD", - "price": 5, - "value": 25.0, - "invoiceDesc": "" - }, "storage": { "name": "", "unit": "GB", @@ -145,38 +126,9 @@ def test_get(self, m): "price": 5, "value": 25.0, "invoiceDesc": "" - }, - "credits": { - "name": "", - "unit": "GB", - "currency": "USD", - "price": 5, - "value": 25.0, - "invoiceDesc": "" - } - }, - "addons": { - "seats": { - "supported": True, - "planIncluded": 1.0, - "limit": 5.0, - "type": "numeric", - "currency": "USD", - "price": 5, - "value": 25.0, - "invoiceDesc": "" - }, - "projects": { - "supported": True, - "planIncluded": 1.0, - "limit": 5.0, - "type": "numeric", - "currency": "USD", - "price": 5, - "value": 25.0, - "invoiceDesc": "" } }, + "addons": {}, "budgetCapEnabled": True, "customSmtp": True, "emailBranding": True, @@ -194,14 +146,12 @@ def test_get(self, m): "supportsFreeEmailValidation": True, "supportsCorporateEmailValidation": True, "supportsProjectSpecificRoles": True, - "backupsEnabled": True, "usagePerProject": True, "supportedAddons": { "baa": True, "premiumGeoDB": True, "premiumGeoDBOrg": True }, - "backupPolicies": 1.0, "deploymentSize": 30.0, "buildSize": 2000.0, "databasesAllowEncrypt": True, @@ -211,22 +161,11 @@ def test_get(self, m): "billingStartDate": "2020-10-15T06:38:00.000+00:00", "billingCurrentInvoiceDate": "2020-10-15T06:38:00.000+00:00", "billingNextInvoiceDate": "2020-10-15T06:38:00.000+00:00", - "billingTrialStartDate": "2020-10-15T06:38:00.000+00:00", "billingTrialDays": 14.0, "billingAggregationId": "adbc3de4rddfsd", "billingInvoiceId": "adbc3de4rddfsd", "paymentMethodId": "adbc3de4rddfsd", - "billingAddressId": "adbc3de4rddfsd", - "backupPaymentMethodId": "adbc3de4rddfsd", "status": "active", - "remarks": "Pending initial payment", - "agreementBAA": "", - "programManagerName": "", - "programManagerCalendar": "", - "programDiscordChannelName": "", - "programDiscordChannelUrl": "", - "billingPlanDowngrade": "tier-1", - "billingTaxId": "", "markedForDeletion": True, "platform": "imagine", "projects": [] @@ -248,7 +187,6 @@ def test_update(self, m): "name": "VIP", "total": 7.0, "prefs": {}, - "billingBudget": 50.0, "budgetAlerts": [], "billingPlan": "tier-1", "billingPlanId": "tier-1", @@ -263,7 +201,6 @@ def test_update(self, m): "storage": 25.0, "imageTransformations": 100.0, "screenshotsGenerated": 50.0, - "members": 25.0, "webhooks": 25.0, "wafRules": 2.0, "projects": 2.0, @@ -287,7 +224,6 @@ def test_update(self, m): "topics": 1.0, "authPhone": 10.0, "domains": 5.0, - "activityLogs": 7.0, "usageLogs": 30.0, "projectInactivityDays": 7.0, "alertLimit": 80.0, @@ -308,14 +244,6 @@ def test_update(self, m): "value": 25.0, "invoiceDesc": "" }, - "member": { - "name": "", - "unit": "GB", - "currency": "USD", - "price": 5, - "value": 25.0, - "invoiceDesc": "" - }, "realtime": { "name": "", "unit": "GB", @@ -332,14 +260,6 @@ def test_update(self, m): "value": 25.0, "invoiceDesc": "" }, - "realtimeBandwidth": { - "name": "", - "unit": "GB", - "currency": "USD", - "price": 5, - "value": 25.0, - "invoiceDesc": "" - }, "storage": { "name": "", "unit": "GB", @@ -371,38 +291,9 @@ def test_update(self, m): "price": 5, "value": 25.0, "invoiceDesc": "" - }, - "credits": { - "name": "", - "unit": "GB", - "currency": "USD", - "price": 5, - "value": 25.0, - "invoiceDesc": "" - } - }, - "addons": { - "seats": { - "supported": True, - "planIncluded": 1.0, - "limit": 5.0, - "type": "numeric", - "currency": "USD", - "price": 5, - "value": 25.0, - "invoiceDesc": "" - }, - "projects": { - "supported": True, - "planIncluded": 1.0, - "limit": 5.0, - "type": "numeric", - "currency": "USD", - "price": 5, - "value": 25.0, - "invoiceDesc": "" } }, + "addons": {}, "budgetCapEnabled": True, "customSmtp": True, "emailBranding": True, @@ -420,14 +311,12 @@ def test_update(self, m): "supportsFreeEmailValidation": True, "supportsCorporateEmailValidation": True, "supportsProjectSpecificRoles": True, - "backupsEnabled": True, "usagePerProject": True, "supportedAddons": { "baa": True, "premiumGeoDB": True, "premiumGeoDBOrg": True }, - "backupPolicies": 1.0, "deploymentSize": 30.0, "buildSize": 2000.0, "databasesAllowEncrypt": True, @@ -437,22 +326,11 @@ def test_update(self, m): "billingStartDate": "2020-10-15T06:38:00.000+00:00", "billingCurrentInvoiceDate": "2020-10-15T06:38:00.000+00:00", "billingNextInvoiceDate": "2020-10-15T06:38:00.000+00:00", - "billingTrialStartDate": "2020-10-15T06:38:00.000+00:00", "billingTrialDays": 14.0, "billingAggregationId": "adbc3de4rddfsd", "billingInvoiceId": "adbc3de4rddfsd", "paymentMethodId": "adbc3de4rddfsd", - "billingAddressId": "adbc3de4rddfsd", - "backupPaymentMethodId": "adbc3de4rddfsd", "status": "active", - "remarks": "Pending initial payment", - "agreementBAA": "", - "programManagerName": "", - "programManagerCalendar": "", - "programDiscordChannelName": "", - "programDiscordChannelUrl": "", - "billingPlanDowngrade": "tier-1", - "billingTaxId": "", "markedForDeletion": True, "platform": "imagine", "projects": [] diff --git a/test/services/test_project.py b/test/services/test_project.py index b4b9c640..9f8f36e0 100644 --- a/test/services/test_project.py +++ b/test/services/test_project.py @@ -120,30 +120,6 @@ def test_list_keys(self, m): self.assertEqual(response.to_dict(), data) - @requests_mock.Mocker() - def test_create_key(self, m): - data = { - "$id": "5e5ea5c16897e", - "$createdAt": "2020-10-15T06:38:00.000+00:00", - "$updatedAt": "2020-10-15T06:38:00.000+00:00", - "name": "My API Key", - "expire": "2020-10-15T06:38:00.000+00:00", - "scopes": [], - "secret": "919c2d18fb5d4...a2ae413da83346ad2", - "accessedAt": "2020-10-15T06:38:00.000+00:00", - "sdks": [] -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.project.create_key( - '', - '', - [], - ) - - self.assertEqual(response.to_dict(), data) - @requests_mock.Mocker() def test_create_ephemeral_key(self, m): data = { @@ -1596,6 +1572,46 @@ def test_update_membership_privacy_policy(self, m): self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() + def test_update_mfa_factors_policy(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "name": "New Project", + "teamId": "1592981250", + "region": "fra", + "devKeys": [], + "smtpEnabled": True, + "smtpSenderName": "John Appwrite", + "smtpSenderEmail": "john@appwrite.io", + "smtpReplyToName": "Support Team", + "smtpReplyToEmail": "support@appwrite.io", + "smtpHost": "mail.appwrite.io", + "smtpPort": 25.0, + "smtpUsername": "emailuser", + "smtpPassword": "smtp-password", + "smtpSecure": "tls", + "pingCount": 1.0, + "pingedAt": "2020-10-15T06:38:00.000+00:00", + "labels": [], + "status": "active", + "onboarding": {}, + "authMethods": [], + "services": [], + "protocols": [], + "blocks": [], + "consoleAccessedAt": "2020-10-15T06:38:00.000+00:00", + "wafEnabled": True +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.project.update_mfa_factors_policy( + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_update_password_dictionary_policy(self, m): data = { diff --git a/test/services/test_proxy.py b/test/services/test_proxy.py index 5da8c18c..0891cc85 100644 --- a/test/services/test_proxy.py +++ b/test/services/test_proxy.py @@ -13,6 +13,24 @@ def setUp(self): self.client = Client() self.proxy = Proxy(self.client) + @requests_mock.Mocker() + def test_create_invalidation(self, m): + data = { + "domain": "appwrite.company.com", + "type": "tag", + "reference": "products", + "status": "success" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.proxy.create_invalidation( + '', + 'tag', + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_list_rules(self, m): data = { diff --git a/test/services/test_storage.py b/test/services/test_storage.py index 08ca786e..af4587cb 100644 --- a/test/services/test_storage.py +++ b/test/services/test_storage.py @@ -146,6 +146,8 @@ def test_create_file(self, m): "$updatedAt": "2020-10-15T06:38:00.000+00:00", "$permissions": [], "name": "Pink.png", + "folder": "photos\/2026\/", + "key": "photos\/2026\/Pink.png", "signature": "5d529fd02b544198ae075bd57c1762bb", "mimeType": "image\/png", "sizeOriginal": 17890.0, @@ -175,6 +177,8 @@ def test_get_file(self, m): "$updatedAt": "2020-10-15T06:38:00.000+00:00", "$permissions": [], "name": "Pink.png", + "folder": "photos\/2026\/", + "key": "photos\/2026\/Pink.png", "signature": "5d529fd02b544198ae075bd57c1762bb", "mimeType": "image\/png", "sizeOriginal": 17890.0, @@ -203,6 +207,8 @@ def test_update_file(self, m): "$updatedAt": "2020-10-15T06:38:00.000+00:00", "$permissions": [], "name": "Pink.png", + "folder": "photos\/2026\/", + "key": "photos\/2026\/Pink.png", "signature": "5d529fd02b544198ae075bd57c1762bb", "mimeType": "image\/png", "sizeOriginal": 17890.0, diff --git a/test/services/test_tables_db.py b/test/services/test_tables_db.py index 888be71f..ba339712 100644 --- a/test/services/test_tables_db.py +++ b/test/services/test_tables_db.py @@ -56,7 +56,6 @@ def test_list_specifications(self, m): "storageOverageRate": 0.125, "bandwidthOverageRate": 0.08, "replicaRate": 1, - "crossRegionReplicaRate": 1, "pitrRate": 0.2 } } @@ -250,7 +249,6 @@ def test_create_failover(self, m): "nodePool": "db-pool-4vcpu-8gb", "replicas": 2.0, "syncMode": "async", - "crossRegionReplicas": 1.0, "networkMaxConnections": 500.0, "networkIdleTimeoutSeconds": 900.0, "networkIPAllowlist": [], @@ -279,11 +277,147 @@ def test_create_failover(self, m): self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() + def test_list_migrations(self, m): + data = { + "total": 5.0, + "migrations": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.tables_db.list_migrations( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_create_migration(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "projectId": "5e5ea5c16897e", + "databaseId": "5e5ea5c16897e", + "specification": "s-2vcpu-4gb", + "phase": "pending", + "attempt": 0.0, + "lastError": "", + "lagDocuments": 0.0, + "verifiedAt": "2020-10-15T06:38:00.000+00:00", + "cutoverAt": "2020-10-15T06:38:00.000+00:00", + "soakUntil": "2020-10-15T06:38:00.000+00:00", + "autoCutover": True, + "cutoverRequested": True, + "paused": True +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.tables_db.create_migration( + '', + 's-1vcpu-1gb', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_migration(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "projectId": "5e5ea5c16897e", + "databaseId": "5e5ea5c16897e", + "specification": "s-2vcpu-4gb", + "phase": "pending", + "attempt": 0.0, + "lastError": "", + "lagDocuments": 0.0, + "verifiedAt": "2020-10-15T06:38:00.000+00:00", + "cutoverAt": "2020-10-15T06:38:00.000+00:00", + "soakUntil": "2020-10-15T06:38:00.000+00:00", + "autoCutover": True, + "cutoverRequested": True, + "paused": True +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.tables_db.get_migration( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_delete_migration(self, m): + data = '' + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.tables_db.delete_migration( + '', + '', + ) + + self.assertEqual(response, data) + + @requests_mock.Mocker() + def test_cutover_migration(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "projectId": "5e5ea5c16897e", + "databaseId": "5e5ea5c16897e", + "specification": "s-2vcpu-4gb", + "phase": "pending", + "attempt": 0.0, + "lastError": "", + "lagDocuments": 0.0, + "verifiedAt": "2020-10-15T06:38:00.000+00:00", + "cutoverAt": "2020-10-15T06:38:00.000+00:00", + "soakUntil": "2020-10-15T06:38:00.000+00:00", + "autoCutover": True, + "cutoverRequested": True, + "paused": True +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.tables_db.cutover_migration( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_list_operations(self, m): + data = { + "total": 5.0, + "operations": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.tables_db.list_operations( + '', + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_get_replicas(self, m): data = { "replicas": 2.0, "syncMode": "async", + "syncDegraded": True, + "syncAcknowledgements": 1.0, + "syncStandbyCount": 2.0, "members": [] } headers = {'Content-Type': 'application/json'} @@ -307,6 +441,10 @@ def test_get_status(self, m): "current": 12.0, "max": 100.0 }, + "syncMode": "async", + "syncDegraded": True, + "syncAcknowledgements": 1.0, + "syncStandbyCount": 2.0, "replicas": [], "volumes": [] } diff --git a/test/services/test_teams.py b/test/services/test_teams.py index 684d00f8..9c2635f5 100644 --- a/test/services/test_teams.py +++ b/test/services/test_teams.py @@ -352,7 +352,6 @@ def test_get_prefs(self, m): '', ) - data['data'] = {} self.assertEqual(response.to_dict(), data) @requests_mock.Mocker() @@ -366,6 +365,5 @@ def test_update_prefs(self, m): {}, ) - data['data'] = {} self.assertEqual(response.to_dict(), data) diff --git a/test/services/test_users.py b/test/services/test_users.py index cd4eb71f..efb2130d 100644 --- a/test/services/test_users.py +++ b/test/services/test_users.py @@ -568,13 +568,33 @@ def test_delete_mfa_authenticator(self, m): self.assertEqual(response, data) + @requests_mock.Mocker() + def test_get_mfa_challenge(self, m): + data = { + "$id": "bb8ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "userId": "5e5ea5c168bb8", + "expire": "2020-10-15T06:38:00.000+00:00", + "code": "446372" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.users.get_mfa_challenge( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_list_mfa_factors(self, m): data = { "totp": True, "phone": True, "email": True, - "recoveryCode": True + "recoveryCode": True, + "custom": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -591,7 +611,8 @@ def test_list_mfa_factors(self, m): "totp": True, "phone": True, "email": True, - "recoveryCode": True + "recoveryCode": True, + "custom": True } headers = {'Content-Type': 'application/json'} m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) @@ -786,7 +807,6 @@ def test_get_prefs(self, m): '', ) - data['data'] = {} self.assertEqual(response.to_dict(), data) @requests_mock.Mocker() @@ -800,7 +820,6 @@ def test_update_prefs(self, m): {}, ) - data['data'] = {} self.assertEqual(response.to_dict(), data) @requests_mock.Mocker() From a4d7671f3ae9afc7c46420c0a6652bf164b28d3e Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 14 Aug 2026 13:50:41 +0530 Subject: [PATCH 7/8] chore: update Python SDK to 23.0.0 --- CHANGELOG.md | 1 - appwrite/encoders/value_class_encoder.py | 4 -- appwrite/enums/embedding_model.py | 7 --- appwrite/models/__init__.py | 4 -- appwrite/models/embedding.py | 24 -------- appwrite/models/embedding_list.py | 19 ------ appwrite/services/embeddings.py | 58 ------------------- .../embeddings/create-text-embeddings.md | 20 ------- test/services/test_embeddings.py | 30 ---------- 9 files changed, 167 deletions(-) delete mode 100644 appwrite/enums/embedding_model.py delete mode 100644 appwrite/models/embedding.py delete mode 100644 appwrite/models/embedding_list.py delete mode 100644 appwrite/services/embeddings.py delete mode 100644 docs/examples/embeddings/create-text-embeddings.md delete mode 100644 test/services/test_embeddings.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 52fb6a98..fcdb057d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,6 @@ * Breaking: removed `account.createJWT`; use `users.createJWT` instead. A leaked JWT could mint further JWTs, letting a credential outlive its own expiry — a session cannot duplicate itself to live forever either * Breaking: removed `project.createKey`. A leaked key could mint further hidden keys, making a compromise far harder to contain and revoke * Breaking: `activities.listEvents` `queries` now takes an array instead of a string -* Added: `embeddings` service with `createTextEmbeddings`, plus the `EmbeddingModel` enum * Added: TablesDB migration methods `listMigrations`, `createMigration`, `getMigration`, `deleteMigration`, `cutoverMigration`, and `listOperations` * Added: `proxy.createInvalidation` for purging cached edge responses, plus the `InvalidationType` enum * Added: `apps.deleteInstallation` diff --git a/appwrite/encoders/value_class_encoder.py b/appwrite/encoders/value_class_encoder.py index ab46a105..3a70a3d0 100644 --- a/appwrite/encoders/value_class_encoder.py +++ b/appwrite/encoders/value_class_encoder.py @@ -15,7 +15,6 @@ from ..enums.relation_mutate import RelationMutate from ..enums.databases_index_type import DatabasesIndexType from ..enums.order_by import OrderBy -from ..enums.embedding_model import EmbeddingModel from ..enums.runtime import Runtime from ..enums.project_key_scopes import ProjectKeyScopes from ..enums.template_reference_type import TemplateReferenceType @@ -113,9 +112,6 @@ def default(self, o): if isinstance(o, OrderBy): return o.value - if isinstance(o, EmbeddingModel): - return o.value - if isinstance(o, Runtime): return o.value diff --git a/appwrite/enums/embedding_model.py b/appwrite/enums/embedding_model.py deleted file mode 100644 index 5bbf1cf9..00000000 --- a/appwrite/enums/embedding_model.py +++ /dev/null @@ -1,7 +0,0 @@ -from enum import Enum - -class EmbeddingModel(Enum): - NOMIC_EMBED_TEXT = "nomic-embed-text" - EMBEDDING_GEMMA = "embedding-gemma" - ALL_MINILM = "all-minilm" - BGE_SMALL = "bge-small" diff --git a/appwrite/models/__init__.py b/appwrite/models/__init__.py index 9b7d0ced..5d001968 100644 --- a/appwrite/models/__init__.py +++ b/appwrite/models/__init__.py @@ -43,11 +43,9 @@ from .target_list import TargetList from .transaction_list import TransactionList from .specification_list import SpecificationList -from .embedding_list import EmbeddingList from .insight_list import InsightList from .report_list import ReportList from .database import Database -from .embedding import Embedding from .collection import Collection from .attribute_list import AttributeList from .attribute_string import AttributeString @@ -325,11 +323,9 @@ 'TargetList', 'TransactionList', 'SpecificationList', - 'EmbeddingList', 'InsightList', 'ReportList', 'Database', - 'Embedding', 'Collection', 'AttributeList', 'AttributeString', diff --git a/appwrite/models/embedding.py b/appwrite/models/embedding.py deleted file mode 100644 index 9da9bab5..00000000 --- a/appwrite/models/embedding.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Any, Dict, List, Optional, Union, cast -from pydantic import Field, PrivateAttr - -from .base_model import AppwriteModel - -class Embedding(AppwriteModel): - """ - Embedding - - Attributes - ---------- - model : str - Embedding model used to generate embeddings. - dimension : float - Number of dimensions for each embedding vector. - embedding : List[Any] - Embedding vector values. If an error occurs, this will be an empty array. - error : str - Error message if embedding generation fails. Empty string if no error. - """ - model: str = Field(..., alias='model') - dimension: float = Field(..., alias='dimension') - embedding: List[Any] = Field(..., alias='embedding') - error: str = Field(..., alias='error') diff --git a/appwrite/models/embedding_list.py b/appwrite/models/embedding_list.py deleted file mode 100644 index d8f437e7..00000000 --- a/appwrite/models/embedding_list.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Any, Dict, List, Optional, Union, cast -from pydantic import Field, PrivateAttr - -from .base_model import AppwriteModel -from .embedding import Embedding - -class EmbeddingList(AppwriteModel): - """ - Embedding list - - Attributes - ---------- - total : float - Total number of embeddings that matched your query. - embeddings : List[Embedding] - List of embeddings. - """ - total: float = Field(..., alias='total') - embeddings: List[Embedding] = Field(..., alias='embeddings') diff --git a/appwrite/services/embeddings.py b/appwrite/services/embeddings.py deleted file mode 100644 index 50848d06..00000000 --- a/appwrite/services/embeddings.py +++ /dev/null @@ -1,58 +0,0 @@ -from ..service import Service -from urllib.parse import quote -from typing import Any, Dict, List, Optional, Union -from ..exception import AppwriteException -from appwrite.utils.deprecated import deprecated -from ..enums.embedding_model import EmbeddingModel -from ..models.embedding_list import EmbeddingList - -class Embeddings(Service): - - def __init__(self, client) -> None: - super(Embeddings, self).__init__(client) - - def create_text_embeddings( - self, - texts: List[str], - model: Optional[EmbeddingModel] = None - ) -> EmbeddingList: - """ - Generate vector embeddings for an array of text using the selected embedding model. Use the returned vectors to power semantic search and similarity queries against your vector collections. - - - Parameters - ---------- - texts : List[str] - Array of text to generate embeddings. - model : Optional[EmbeddingModel] - The embedding model to use for generating vector embeddings. - - Returns - ------- - EmbeddingList - API response as a typed Pydantic model - - Raises - ------ - AppwriteException - If API request fails - """ - - api_path = '/embeddings/text' - api_params = {} - if texts is None: - raise AppwriteException('Missing required parameter: "texts"') - - - api_params['texts'] = self._normalize_value(texts) - if model is not None: - api_params['model'] = self._normalize_value(model) - - response = self.client.call('post', api_path, { - 'X-Appwrite-Project': self.client.get_config('project'), - 'content-type': 'application/json', - 'accept': 'application/json', - }, api_params) - - return self._parse_response(response, model=EmbeddingList) - diff --git a/docs/examples/embeddings/create-text-embeddings.md b/docs/examples/embeddings/create-text-embeddings.md deleted file mode 100644 index 49d3c258..00000000 --- a/docs/examples/embeddings/create-text-embeddings.md +++ /dev/null @@ -1,20 +0,0 @@ -```python -from appwrite.client import Client -from appwrite.services.embeddings import Embeddings -from appwrite.models import EmbeddingList -from appwrite.enums import EmbeddingModel - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint -client.set_project('') # Your project ID -client.set_key('') # Your secret API key - -embeddings = Embeddings(client) - -result: EmbeddingList = embeddings.create_text_embeddings( - texts = [], - model = EmbeddingModel.NOMIC_EMBED_TEXT # optional -) - -print(result.model_dump()) -``` diff --git a/test/services/test_embeddings.py b/test/services/test_embeddings.py deleted file mode 100644 index 8d2cf848..00000000 --- a/test/services/test_embeddings.py +++ /dev/null @@ -1,30 +0,0 @@ -import json -import requests_mock -import unittest - -from appwrite.client import Client -from appwrite.input_file import InputFile -from appwrite.models import * -from appwrite.services.embeddings import Embeddings - -class EmbeddingsServiceTest(unittest.TestCase): - - def setUp(self): - self.client = Client() - self.embeddings = Embeddings(self.client) - - @requests_mock.Mocker() - def test_create_text_embeddings(self, m): - data = { - "total": 5.0, - "embeddings": [] -} - headers = {'Content-Type': 'application/json'} - m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) - - response = self.embeddings.create_text_embeddings( - [], - ) - - self.assertEqual(response.to_dict(), data) - From 62ea5b878b67c6b7266a5a54e6755d3f543f8698 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 14 Aug 2026 14:00:27 +0530 Subject: [PATCH 8/8] chore: update Python SDK to 23.0.0 --- CHANGELOG.md | 1 + appwrite/encoders/value_class_encoder.py | 4 ++ appwrite/enums/embedding_model.py | 7 +++ appwrite/models/__init__.py | 4 ++ appwrite/models/embedding.py | 24 ++++++++ appwrite/models/embedding_list.py | 19 ++++++ appwrite/services/embeddings.py | 58 +++++++++++++++++++ .../embeddings/create-text-embeddings.md | 20 +++++++ test/services/test_embeddings.py | 30 ++++++++++ 9 files changed, 167 insertions(+) create mode 100644 appwrite/enums/embedding_model.py create mode 100644 appwrite/models/embedding.py create mode 100644 appwrite/models/embedding_list.py create mode 100644 appwrite/services/embeddings.py create mode 100644 docs/examples/embeddings/create-text-embeddings.md create mode 100644 test/services/test_embeddings.py diff --git a/CHANGELOG.md b/CHANGELOG.md index fcdb057d..52fb6a98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Breaking: removed `account.createJWT`; use `users.createJWT` instead. A leaked JWT could mint further JWTs, letting a credential outlive its own expiry — a session cannot duplicate itself to live forever either * Breaking: removed `project.createKey`. A leaked key could mint further hidden keys, making a compromise far harder to contain and revoke * Breaking: `activities.listEvents` `queries` now takes an array instead of a string +* Added: `embeddings` service with `createTextEmbeddings`, plus the `EmbeddingModel` enum * Added: TablesDB migration methods `listMigrations`, `createMigration`, `getMigration`, `deleteMigration`, `cutoverMigration`, and `listOperations` * Added: `proxy.createInvalidation` for purging cached edge responses, plus the `InvalidationType` enum * Added: `apps.deleteInstallation` diff --git a/appwrite/encoders/value_class_encoder.py b/appwrite/encoders/value_class_encoder.py index 3a70a3d0..ab46a105 100644 --- a/appwrite/encoders/value_class_encoder.py +++ b/appwrite/encoders/value_class_encoder.py @@ -15,6 +15,7 @@ from ..enums.relation_mutate import RelationMutate from ..enums.databases_index_type import DatabasesIndexType from ..enums.order_by import OrderBy +from ..enums.embedding_model import EmbeddingModel from ..enums.runtime import Runtime from ..enums.project_key_scopes import ProjectKeyScopes from ..enums.template_reference_type import TemplateReferenceType @@ -112,6 +113,9 @@ def default(self, o): if isinstance(o, OrderBy): return o.value + if isinstance(o, EmbeddingModel): + return o.value + if isinstance(o, Runtime): return o.value diff --git a/appwrite/enums/embedding_model.py b/appwrite/enums/embedding_model.py new file mode 100644 index 00000000..5bbf1cf9 --- /dev/null +++ b/appwrite/enums/embedding_model.py @@ -0,0 +1,7 @@ +from enum import Enum + +class EmbeddingModel(Enum): + NOMIC_EMBED_TEXT = "nomic-embed-text" + EMBEDDING_GEMMA = "embedding-gemma" + ALL_MINILM = "all-minilm" + BGE_SMALL = "bge-small" diff --git a/appwrite/models/__init__.py b/appwrite/models/__init__.py index 5d001968..9b7d0ced 100644 --- a/appwrite/models/__init__.py +++ b/appwrite/models/__init__.py @@ -43,9 +43,11 @@ from .target_list import TargetList from .transaction_list import TransactionList from .specification_list import SpecificationList +from .embedding_list import EmbeddingList from .insight_list import InsightList from .report_list import ReportList from .database import Database +from .embedding import Embedding from .collection import Collection from .attribute_list import AttributeList from .attribute_string import AttributeString @@ -323,9 +325,11 @@ 'TargetList', 'TransactionList', 'SpecificationList', + 'EmbeddingList', 'InsightList', 'ReportList', 'Database', + 'Embedding', 'Collection', 'AttributeList', 'AttributeString', diff --git a/appwrite/models/embedding.py b/appwrite/models/embedding.py new file mode 100644 index 00000000..9da9bab5 --- /dev/null +++ b/appwrite/models/embedding.py @@ -0,0 +1,24 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class Embedding(AppwriteModel): + """ + Embedding + + Attributes + ---------- + model : str + Embedding model used to generate embeddings. + dimension : float + Number of dimensions for each embedding vector. + embedding : List[Any] + Embedding vector values. If an error occurs, this will be an empty array. + error : str + Error message if embedding generation fails. Empty string if no error. + """ + model: str = Field(..., alias='model') + dimension: float = Field(..., alias='dimension') + embedding: List[Any] = Field(..., alias='embedding') + error: str = Field(..., alias='error') diff --git a/appwrite/models/embedding_list.py b/appwrite/models/embedding_list.py new file mode 100644 index 00000000..d8f437e7 --- /dev/null +++ b/appwrite/models/embedding_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .embedding import Embedding + +class EmbeddingList(AppwriteModel): + """ + Embedding list + + Attributes + ---------- + total : float + Total number of embeddings that matched your query. + embeddings : List[Embedding] + List of embeddings. + """ + total: float = Field(..., alias='total') + embeddings: List[Embedding] = Field(..., alias='embeddings') diff --git a/appwrite/services/embeddings.py b/appwrite/services/embeddings.py new file mode 100644 index 00000000..50848d06 --- /dev/null +++ b/appwrite/services/embeddings.py @@ -0,0 +1,58 @@ +from ..service import Service +from urllib.parse import quote +from typing import Any, Dict, List, Optional, Union +from ..exception import AppwriteException +from appwrite.utils.deprecated import deprecated +from ..enums.embedding_model import EmbeddingModel +from ..models.embedding_list import EmbeddingList + +class Embeddings(Service): + + def __init__(self, client) -> None: + super(Embeddings, self).__init__(client) + + def create_text_embeddings( + self, + texts: List[str], + model: Optional[EmbeddingModel] = None + ) -> EmbeddingList: + """ + Generate vector embeddings for an array of text using the selected embedding model. Use the returned vectors to power semantic search and similarity queries against your vector collections. + + + Parameters + ---------- + texts : List[str] + Array of text to generate embeddings. + model : Optional[EmbeddingModel] + The embedding model to use for generating vector embeddings. + + Returns + ------- + EmbeddingList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/embeddings/text' + api_params = {} + if texts is None: + raise AppwriteException('Missing required parameter: "texts"') + + + api_params['texts'] = self._normalize_value(texts) + if model is not None: + api_params['model'] = self._normalize_value(model) + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=EmbeddingList) + diff --git a/docs/examples/embeddings/create-text-embeddings.md b/docs/examples/embeddings/create-text-embeddings.md new file mode 100644 index 00000000..49d3c258 --- /dev/null +++ b/docs/examples/embeddings/create-text-embeddings.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.embeddings import Embeddings +from appwrite.models import EmbeddingList +from appwrite.enums import EmbeddingModel + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +embeddings = Embeddings(client) + +result: EmbeddingList = embeddings.create_text_embeddings( + texts = [], + model = EmbeddingModel.NOMIC_EMBED_TEXT # optional +) + +print(result.model_dump()) +``` diff --git a/test/services/test_embeddings.py b/test/services/test_embeddings.py new file mode 100644 index 00000000..8d2cf848 --- /dev/null +++ b/test/services/test_embeddings.py @@ -0,0 +1,30 @@ +import json +import requests_mock +import unittest + +from appwrite.client import Client +from appwrite.input_file import InputFile +from appwrite.models import * +from appwrite.services.embeddings import Embeddings + +class EmbeddingsServiceTest(unittest.TestCase): + + def setUp(self): + self.client = Client() + self.embeddings = Embeddings(self.client) + + @requests_mock.Mocker() + def test_create_text_embeddings(self, m): + data = { + "total": 5.0, + "embeddings": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.embeddings.create_text_embeddings( + [], + ) + + self.assertEqual(response.to_dict(), data) +