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 = {