diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f0e30cb5..53679c3b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,10 +9,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.9' diff --git a/CHANGELOG.md b/CHANGELOG.md index d0f3fdfd..4eba1752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## 22.2.0 + +* Added: `apps` installation and key management methods (`list_installations`, `get_installation`, `create_installation_token`, `list_installation_scopes`, `list_keys`, `create_key`, `get_key`, `delete_key`) +* Added: `installation_scopes` and `installation_redirect_url` parameters to `apps.update` +* Added: app installation management methods to `organization` and `teams` services +* Added: `installation_access_token_duration` parameter to `project.update_o_auth2_server` +* Added: `installationScopes` and `installationRedirectUrl` fields to `App` model +* Added: `organization.installations.read`/`organization.installations.write` key scopes + ## 22.1.0 * Updated: removed `new_specification` parameter from `backups.create_restoration` diff --git a/appwrite/client.py b/appwrite/client.py index 3588e3a7..6e1d40e7 100644 --- a/appwrite/client.py +++ b/appwrite/client.py @@ -17,11 +17,11 @@ def __init__(self): self._endpoint = 'https://cloud.appwrite.io/v1' self._global_headers = { 'content-type': '', - 'user-agent' : f'AppwritePythonSDK/22.1.0 ({platform.uname().system}; {platform.uname().version}; {platform.uname().machine})', + 'user-agent' : f'AppwritePythonSDK/22.2.0 ({platform.uname().system}; {platform.uname().version}; {platform.uname().machine})', 'x-sdk-name': 'Python', 'x-sdk-platform': 'server', 'x-sdk-language': 'python', - 'x-sdk-version': '22.1.0', + 'x-sdk-version': '22.2.0', 'X-Appwrite-Response-Format' : '1.9.5', } self._config = {} diff --git a/appwrite/enums/organization_key_scopes.py b/appwrite/enums/organization_key_scopes.py index ab4e4526..bb2515df 100644 --- a/appwrite/enums/organization_key_scopes.py +++ b/appwrite/enums/organization_key_scopes.py @@ -7,6 +7,8 @@ class OrganizationKeyScopes(Enum): DEVKEYS_WRITE = "devKeys.write" ORGANIZATION_KEYS_READ = "organization.keys.read" ORGANIZATION_KEYS_WRITE = "organization.keys.write" + ORGANIZATION_INSTALLATIONS_READ = "organization.installations.read" + ORGANIZATION_INSTALLATIONS_WRITE = "organization.installations.write" ORGANIZATION_MEMBERSHIPS_READ = "organization.memberships.read" ORGANIZATION_MEMBERSHIPS_WRITE = "organization.memberships.write" ORGANIZATION_READ = "organization.read" diff --git a/appwrite/models/__init__.py b/appwrite/models/__init__.py index 07a73030..419db211 100644 --- a/appwrite/models/__init__.py +++ b/appwrite/models/__init__.py @@ -243,6 +243,8 @@ from .app_secret import AppSecret from .app_secret_plaintext import AppSecretPlaintext from .app_scope import AppScope +from .app_installation import AppInstallation +from .app_key import AppKey from .oauth2_authorize import Oauth2Authorize from .oauth2_approve import Oauth2Approve from .oauth2_reject import Oauth2Reject @@ -265,6 +267,8 @@ from .apps_list import AppsList from .app_secret_list import AppSecretList from .app_scope_list import AppScopeList +from .app_installation_list import AppInstallationList +from .app_key_list import AppKeyList __all__ = [ 'AppwriteModel', @@ -512,6 +516,8 @@ 'AppSecret', 'AppSecretPlaintext', 'AppScope', + 'AppInstallation', + 'AppKey', 'Oauth2Authorize', 'Oauth2Approve', 'Oauth2Reject', @@ -534,4 +540,6 @@ 'AppsList', 'AppSecretList', 'AppScopeList', + 'AppInstallationList', + 'AppKeyList', ] diff --git a/appwrite/models/app.py b/appwrite/models/app.py index f570a66c..76e93798 100644 --- a/appwrite/models/app.py +++ b/appwrite/models/app.py @@ -56,6 +56,10 @@ class App(AppwriteModel): ID of team that owns the application, if owned by team. Otherwise, user ID will be used. userid : str ID of user who owns the application, if owned by user. Otherwise, team ID will be used. + installationscopes : List[Any] + Scopes the application requests when installed on a team. Organization-level and project-level scopes only. + installationredirecturl : str + URL users are redirected to after creating or updating an installation of this application. Empty for no redirect. secrets : List[AppSecret] List of application secrets. """ @@ -82,4 +86,6 @@ class App(AppwriteModel): deviceflow: bool = Field(..., alias='deviceFlow') teamid: str = Field(..., alias='teamId') userid: str = Field(..., alias='userId') + installationscopes: List[Any] = Field(..., alias='installationScopes') + installationredirecturl: str = Field(..., alias='installationRedirectUrl') secrets: List[AppSecret] = Field(..., alias='secrets') diff --git a/appwrite/models/app_installation.py b/appwrite/models/app_installation.py new file mode 100644 index 00000000..3984389c --- /dev/null +++ b/appwrite/models/app_installation.py @@ -0,0 +1,42 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class AppInstallation(AppwriteModel): + """ + AppInstallation + + Attributes + ---------- + id : str + Installation ID. + createdat : str + Installation creation time in ISO 8601 format. + updatedat : str + Installation update time in ISO 8601 format. + appid : str + ID of the installed application. + teamid : str + ID of the team the application is installed on. + scopes : List[Any] + Scopes granted to the application. Snapshot of the application's installation scopes taken when the installation was created or last updated. + authorizationdetails : Dict[str, Any] + Authorization details granted to the application. Rich authorization request (RFC 9396) style entries; the Appwrite Console stores authorized project IDs here. + createdbyid : str + ID of the user who created the installation. + createdbyname : str + Name of the user who created the installation. + lastaccessedat : Optional[str] + Time an access token was last issued for the installation in ISO 8601 format. Null if never used. + """ + id: str = Field(..., alias='$id') + createdat: str = Field(..., alias='$createdAt') + updatedat: str = Field(..., alias='$updatedAt') + appid: str = Field(..., alias='appId') + teamid: str = Field(..., alias='teamId') + scopes: List[Any] = Field(..., alias='scopes') + authorizationdetails: Dict[str, Any] = Field(..., alias='authorizationDetails') + createdbyid: str = Field(..., alias='createdById') + createdbyname: str = Field(..., alias='createdByName') + lastaccessedat: Optional[str] = Field(default=None, alias='lastAccessedAt') diff --git a/appwrite/models/app_installation_list.py b/appwrite/models/app_installation_list.py new file mode 100644 index 00000000..0519077d --- /dev/null +++ b/appwrite/models/app_installation_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .app_installation import AppInstallation + +class AppInstallationList(AppwriteModel): + """ + App installations list + + Attributes + ---------- + total : float + Total number of installations that matched your query. + installations : List[AppInstallation] + List of installations. + """ + total: float = Field(..., alias='total') + installations: List[AppInstallation] = Field(..., alias='installations') diff --git a/appwrite/models/app_key.py b/appwrite/models/app_key.py new file mode 100644 index 00000000..88a319f2 --- /dev/null +++ b/appwrite/models/app_key.py @@ -0,0 +1,39 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel + +class AppKey(AppwriteModel): + """ + AppKey + + Attributes + ---------- + id : str + App key ID. + createdat : str + App key creation time in ISO 8601 format. + updatedat : str + App key update time in ISO 8601 format. + appid : str + Application ID this app key belongs to. + secret : str + App key secret. + hint : str + Last few characters of the app key secret, used to help identify it. + createdbyid : str + ID of the user who created the app key. + createdbyname : str + Name of the user who created the app key. + lastaccessedat : Optional[str] + Time the app key was last used for authentication in ISO 8601 format. Null if never used. + """ + id: str = Field(..., alias='$id') + createdat: str = Field(..., alias='$createdAt') + updatedat: str = Field(..., alias='$updatedAt') + appid: str = Field(..., alias='appId') + secret: str = Field(..., alias='secret') + hint: str = Field(..., alias='hint') + createdbyid: str = Field(..., alias='createdById') + createdbyname: str = Field(..., alias='createdByName') + lastaccessedat: Optional[str] = Field(default=None, alias='lastAccessedAt') diff --git a/appwrite/models/app_key_list.py b/appwrite/models/app_key_list.py new file mode 100644 index 00000000..2d4b7ff4 --- /dev/null +++ b/appwrite/models/app_key_list.py @@ -0,0 +1,19 @@ +from typing import Any, Dict, List, Optional, Union, cast +from pydantic import Field, PrivateAttr + +from .base_model import AppwriteModel +from .app_key import AppKey + +class AppKeyList(AppwriteModel): + """ + App keys list + + Attributes + ---------- + total : float + Total number of keys that matched your query. + keys : List[AppKey] + List of keys. + """ + total: float = Field(..., alias='total') + keys: List[AppKey] = Field(..., alias='keys') diff --git a/appwrite/models/database.py b/appwrite/models/database.py index f83198fd..c1852901 100644 --- a/appwrite/models/database.py +++ b/appwrite/models/database.py @@ -27,6 +27,10 @@ class Database(AppwriteModel): Database type. status : Optional[DatabaseStatus] Dedicated database lifecycle status. Null when the database has no valid dedicated backing. + engine : Optional[str] + Underlying engine of the dedicated backing: postgresql, mysql, mariadb, or mongodb. A managed product (tablesdb, documentsdb, vectorsdb) reports the engine it runs on, so its type and engine can differ. Null when the database has no dedicated backing. + specification : Optional[str] + Compute specification identifier of the dedicated backing, e.g. s-2vcpu-2gb. Null when the database has no dedicated backing. replicas : Optional[float] Number of secondary high availability replicas, excluding the primary. Null when backing configuration is unavailable. policies : Optional[List[BackupPolicy]] @@ -41,6 +45,8 @@ class Database(AppwriteModel): enabled: bool = Field(..., alias='enabled') type: DatabaseType = Field(..., alias='type') status: Optional[DatabaseStatus] = Field(default=None, alias='status') + engine: Optional[str] = Field(default=None, alias='engine') + specification: Optional[str] = Field(default=None, alias='specification') replicas: Optional[float] = Field(default=None, alias='replicas') policies: Optional[List[BackupPolicy]] = Field(default=None, alias='policies') archives: Optional[List[BackupArchive]] = Field(default=None, alias='archives') diff --git a/appwrite/models/project.py b/appwrite/models/project.py index 3dd2ca01..76822354 100644 --- a/appwrite/models/project.py +++ b/appwrite/models/project.py @@ -91,6 +91,8 @@ class Project(AppwriteModel): OAuth2 server access token duration in seconds for public clients (SPAs, mobile, native) oauth2serverpublicrefreshtokenduration : Optional[float] OAuth2 server refresh token duration in seconds for public clients (SPAs, mobile, native) + oauth2serverinstallationaccesstokenduration : Optional[float] + OAuth2 server access token duration in seconds for app installation access tokens oauth2serverconfidentialpkce : Optional[bool] When enabled, PKCE is required for confidential clients (server-side flows using client_secret). PKCE is always required for public clients regardless of this setting. oauth2serververificationurl : Optional[str] @@ -142,6 +144,7 @@ class Project(AppwriteModel): oauth2serverrefreshtokenduration: Optional[float] = Field(default=None, alias='oAuth2ServerRefreshTokenDuration') oauth2serverpublicaccesstokenduration: Optional[float] = Field(default=None, alias='oAuth2ServerPublicAccessTokenDuration') oauth2serverpublicrefreshtokenduration: Optional[float] = Field(default=None, alias='oAuth2ServerPublicRefreshTokenDuration') + oauth2serverinstallationaccesstokenduration: Optional[float] = Field(default=None, alias='oAuth2ServerInstallationAccessTokenDuration') oauth2serverconfidentialpkce: Optional[bool] = Field(default=None, alias='oAuth2ServerConfidentialPkce') oauth2serververificationurl: Optional[str] = Field(default=None, alias='oAuth2ServerVerificationUrl') oauth2serverusercodelength: Optional[float] = Field(default=None, alias='oAuth2ServerUserCodeLength') diff --git a/appwrite/services/apps.py b/appwrite/services/apps.py index 8b8e3285..d4a3f681 100644 --- a/appwrite/services/apps.py +++ b/appwrite/services/apps.py @@ -6,6 +6,11 @@ from ..models.apps_list import AppsList from ..models.app import App from ..models.app_scope_list import AppScopeList +from ..models.app_installation_list import AppInstallationList +from ..models.app_installation import AppInstallation +from ..models.oauth2_token import Oauth2Token +from ..models.app_key_list import AppKeyList +from ..models.app_key import AppKey from ..models.app_secret_list import AppSecretList from ..models.app_secret_plaintext import AppSecretPlaintext from ..models.app_secret import AppSecret @@ -191,6 +196,34 @@ def create( return self._parse_response(response, model=App) + def list_installation_scopes( + self + ) -> AppScopeList: + """ + List scopes an application can request when installed on a team. + + Returns + ------- + AppScopeList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/scopes/installations' + api_params = {} + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppScopeList) + + def list_o_auth2_scopes( self ) -> AppScopeList: @@ -277,7 +310,9 @@ def update( redirect_uris: Optional[List[str]] = None, post_logout_redirect_uris: Optional[List[str]] = None, type: Optional[str] = None, - device_flow: Optional[bool] = None + device_flow: Optional[bool] = None, + installation_scopes: Optional[List[str]] = None, + installation_redirect_url: Optional[str] = None ) -> App: """ Update an application by its unique ID. @@ -320,6 +355,10 @@ def update( OAuth2 client type. Use `public` for SPAs, mobile, and native apps that cannot keep a `client_secret` — PKCE is then required at the token endpoint. Use `confidential` for server-side clients that present a `client_secret`. Defaults to `confidential`. device_flow : Optional[bool] Allow this client to use the OAuth2 Device Authorization Grant (RFC 8628) for input-constrained devices such as TVs and CLIs. Defaults to false. + installation_scopes : Optional[List[str]] + Scopes the application requests when installed on a team. Organization-level and project-level scopes only; use the list scopes endpoint with `type=installation` to discover available values. Maximum of 100 scopes are allowed. + installation_redirect_url : Optional[str] + URL users are redirected to after creating or updating an installation of this application. Must be an https URL, an http loopback URL (localhost, 127.0.0.1, [::1]), or a private-use scheme URI, and must not contain a fragment. Leave empty for no redirect. Returns ------- @@ -375,6 +414,10 @@ def update( api_params['type'] = self._normalize_value(type) if device_flow is not None: api_params['deviceFlow'] = self._normalize_value(device_flow) + if installation_scopes is not None: + api_params['installationScopes'] = self._normalize_value(installation_scopes) + if installation_redirect_url is not None: + api_params['installationRedirectUrl'] = self._normalize_value(installation_redirect_url) response = self.client.call('put', api_path, { 'X-Appwrite-Project': self.client.get_config('project'), @@ -425,6 +468,330 @@ def delete( return response + def list_installations( + self, + app_id: str, + queries: Optional[List[str]] = None, + total: Optional[bool] = None + ) -> AppInstallationList: + """ + List installations of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. + + Parameters + ---------- + app_id : str + Application unique ID. + queries : Optional[List[str]] + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + total : Optional[bool] + When set to false, the total count returned will be 0 and will not be calculated. + + Returns + ------- + AppInstallationList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/installations' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + + if queries is not None: + api_params['queries'] = self._normalize_value(queries) + if total is not None: + api_params['total'] = self._normalize_value(total) + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallationList) + + + def get_installation( + self, + app_id: str, + installation_id: str + ) -> AppInstallation: + """ + Get an installation of an application by its unique ID. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. + + Parameters + ---------- + app_id : str + Application unique ID. + installation_id : str + Installation unique ID. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/installations/{installationId}' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def create_installation_token( + self, + app_id: str, + installation_id: str + ) -> Oauth2Token: + """ + Create a token for an installation of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. The returned token carries the scopes and authorization details granted to the installation, and can be used as an `Authorization: Bearer` header everywhere OAuth2 access tokens are accepted. Multiple tokens can be active for the same installation at once; each token stays valid until it expires or the installation is updated or deleted. + + Parameters + ---------- + app_id : str + Application unique ID. + installation_id : str + Installation unique ID. + + Returns + ------- + Oauth2Token + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/installations/{installationId}/tokens' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=Oauth2Token) + + + def list_keys( + self, + app_id: str, + queries: Optional[List[str]] = None, + total: Optional[bool] = None + ) -> AppKeyList: + """ + List app keys for an application. + + Parameters + ---------- + app_id : str + Application unique ID. + queries : Optional[List[str]] + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + total : Optional[bool] + When set to false, the total count returned will be 0 and will not be calculated. + + Returns + ------- + AppKeyList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/keys' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + + if queries is not None: + api_params['queries'] = self._normalize_value(queries) + if total is not None: + api_params['total'] = self._normalize_value(total) + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppKeyList) + + + def create_key( + self, + app_id: str + ) -> AppKey: + """ + Create a new app key for an application. App keys carry no scopes; send one in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header to list the application's installations and create installation access tokens. + + Parameters + ---------- + app_id : str + Application unique ID. + + Returns + ------- + AppKey + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/keys' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppKey) + + + def get_key( + self, + app_id: str, + key_id: str + ) -> AppKey: + """ + Get an app key by its unique ID. + + Parameters + ---------- + app_id : str + Application unique ID. + key_id : str + App key unique ID. + + Returns + ------- + AppKey + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/keys/{keyId}' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + if key_id is None: + raise AppwriteException('Missing required parameter: "key_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + api_path = api_path.replace('{keyId}', str(self._normalize_value(key_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppKey) + + + def delete_key( + self, + app_id: str, + key_id: str + ) -> Dict[str, Any]: + """ + Delete an app key by its unique ID. + + Parameters + ---------- + app_id : str + Application unique ID. + key_id : str + App key unique ID. + + Returns + ------- + Dict[str, Any] + API response as a dictionary + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/apps/{appId}/keys/{keyId}' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + if key_id is None: + raise AppwriteException('Missing required parameter: "key_id"') + + api_path = api_path.replace('{appId}', str(self._normalize_value(app_id))) + api_path = api_path.replace('{keyId}', str(self._normalize_value(key_id))) + + + response = self.client.call('delete', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return response + + def update_labels( self, app_id: str, diff --git a/appwrite/services/organization.py b/appwrite/services/organization.py index e3f11e50..6ea8aa89 100644 --- a/appwrite/services/organization.py +++ b/appwrite/services/organization.py @@ -4,6 +4,8 @@ from ..exception import AppwriteException from appwrite.utils.deprecated import deprecated from ..models.organization import Organization as OrganizationModel +from ..models.app_installation_list import AppInstallationList +from ..models.app_installation import AppInstallation from ..models.key_list import KeyList from ..enums.organization_key_scopes import OrganizationKeyScopes from ..models.key import Key @@ -127,6 +129,217 @@ def delete( return response + def list_installations( + self, + queries: Optional[List[str]] = None, + total: Optional[bool] = None + ) -> AppInstallationList: + """ + List app installations on the organization. Any organization member can read installations. + + Parameters + ---------- + queries : Optional[List[str]] + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + total : Optional[bool] + When set to false, the total count returned will be 0 and will not be calculated. + + Returns + ------- + AppInstallationList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/installations' + api_params = {} + + if queries is not None: + api_params['queries'] = self._normalize_value(queries) + if total is not None: + api_params['total'] = self._normalize_value(total) + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallationList) + + + def create_installation( + self, + app_id: str, + authorization_details: Optional[str] = None + ) -> AppInstallation: + """ + Install an app on the organization. Only organization members with the owner role can install apps. The installation is granted the scopes the app currently requests. + + Parameters + ---------- + app_id : str + Application unique ID. + authorization_details : Optional[str] + Authorization details granted to the installation as a JSON array of objects, each with a `type` and app-defined fields. The Appwrite Console stores authorized project IDs here. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/installations' + api_params = {} + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + + api_params['appId'] = self._normalize_value(app_id) + if authorization_details is not None: + api_params['authorizationDetails'] = self._normalize_value(authorization_details) + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def get_installation( + self, + installation_id: str + ) -> AppInstallation: + """ + Get an app installation on the organization by its unique ID. Any organization member can read installations. + + Parameters + ---------- + installation_id : str + Installation unique ID. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/installations/{installationId}' + api_params = {} + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def update_installation( + self, + installation_id: str, + authorization_details: Optional[str] = None + ) -> AppInstallation: + """ + Update an app installation on the organization. Only organization members with the owner role can update installations. The installation's granted scopes are refreshed to the scopes the app currently requests; previously issued installation access tokens are revoked. + + Parameters + ---------- + installation_id : str + Installation unique ID. + authorization_details : Optional[str] + Authorization details granted to the installation as a JSON array of objects, each with a `type` and app-defined fields. Omit to keep the current value. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/installations/{installationId}' + api_params = {} + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + if authorization_details is not None: + api_params['authorizationDetails'] = self._normalize_value(authorization_details) + + response = self.client.call('put', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def delete_installation( + self, + installation_id: str + ) -> Dict[str, Any]: + """ + Uninstall an app from the organization by its installation ID. Only organization members with the owner role can remove installations. Previously issued installation access tokens are revoked. + + Parameters + ---------- + installation_id : str + Installation unique ID. + + Returns + ------- + Dict[str, Any] + API response as a dictionary + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/organization/installations/{installationId}' + api_params = {} + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('delete', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return response + + def list_keys( self, queries: Optional[List[str]] = None, diff --git a/appwrite/services/project.py b/appwrite/services/project.py index 573abdae..38af962f 100644 --- a/appwrite/services/project.py +++ b/appwrite/services/project.py @@ -790,6 +790,7 @@ def update_o_auth2_server( refresh_token_duration: Optional[float] = None, public_access_token_duration: Optional[float] = None, public_refresh_token_duration: Optional[float] = None, + installation_access_token_duration: Optional[float] = None, confidential_pkce: Optional[bool] = None, verification_url: Optional[str] = None, user_code_length: Optional[float] = None, @@ -818,6 +819,8 @@ def update_o_auth2_server( Access token duration in seconds for public clients (SPAs, mobile, and native apps that cannot keep a client secret). Leave empty to use default 1 hour. public_refresh_token_duration : Optional[float] Refresh token duration in seconds for public clients (SPAs, mobile, and native apps that cannot keep a client secret). Leave empty to use default 30 days. + installation_access_token_duration : Optional[float] + Access token duration in seconds for app installation access tokens. Leave empty to use default 1 hour. confidential_pkce : Optional[bool] When enabled, PKCE is required for confidential clients (server-side flows using client_secret). PKCE is always required for public clients regardless of this setting. verification_url : Optional[str] @@ -865,6 +868,8 @@ def update_o_auth2_server( api_params['publicAccessTokenDuration'] = self._normalize_value(public_access_token_duration) if public_refresh_token_duration is not None: api_params['publicRefreshTokenDuration'] = self._normalize_value(public_refresh_token_duration) + if installation_access_token_duration is not None: + api_params['installationAccessTokenDuration'] = self._normalize_value(installation_access_token_duration) if confidential_pkce is not None: api_params['confidentialPkce'] = self._normalize_value(confidential_pkce) if verification_url is not None: diff --git a/appwrite/services/teams.py b/appwrite/services/teams.py index 6b8a6fc9..45594204 100644 --- a/appwrite/services/teams.py +++ b/appwrite/services/teams.py @@ -5,6 +5,8 @@ from appwrite.utils.deprecated import deprecated from ..models.team_list import TeamList from ..models.team import Team +from ..models.app_installation_list import AppInstallationList +from ..models.app_installation import AppInstallation from ..models.membership_list import MembershipList from ..models.membership import Membership from ..models.preferences import Preferences @@ -256,6 +258,252 @@ def delete( return response + def list_installations( + self, + team_id: str, + queries: Optional[List[str]] = None, + total: Optional[bool] = None + ) -> AppInstallationList: + """ + List app installations on a team. Any team member can read installations. + + Parameters + ---------- + team_id : str + Team ID. + queries : Optional[List[str]] + Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. + total : Optional[bool] + When set to false, the total count returned will be 0 and will not be calculated. + + Returns + ------- + AppInstallationList + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/teams/{teamId}/installations' + api_params = {} + if team_id is None: + raise AppwriteException('Missing required parameter: "team_id"') + + api_path = api_path.replace('{teamId}', str(self._normalize_value(team_id))) + + if queries is not None: + api_params['queries'] = self._normalize_value(queries) + if total is not None: + api_params['total'] = self._normalize_value(total) + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallationList) + + + def create_installation( + self, + team_id: str, + app_id: str, + authorization_details: Optional[str] = None + ) -> AppInstallation: + """ + Install an app on a team. When authenticated as a user, only team members with the owner role can install apps. Requests using an API key or in admin mode can install apps on any team. The installation is granted the scopes the app currently requests. + + Parameters + ---------- + team_id : str + Team ID. + app_id : str + Application unique ID. + authorization_details : Optional[str] + Authorization details granted to the installation as a JSON array of objects, each with a `type` and app-defined fields. The Appwrite Console stores authorized project IDs here. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/teams/{teamId}/installations' + api_params = {} + if team_id is None: + raise AppwriteException('Missing required parameter: "team_id"') + + if app_id is None: + raise AppwriteException('Missing required parameter: "app_id"') + + api_path = api_path.replace('{teamId}', str(self._normalize_value(team_id))) + + api_params['appId'] = self._normalize_value(app_id) + if authorization_details is not None: + api_params['authorizationDetails'] = self._normalize_value(authorization_details) + + response = self.client.call('post', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def get_installation( + self, + team_id: str, + installation_id: str + ) -> AppInstallation: + """ + Get an app installation on a team by its unique ID. Any team member can read installations. + + Parameters + ---------- + team_id : str + Team ID. + installation_id : str + Installation unique ID. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/teams/{teamId}/installations/{installationId}' + api_params = {} + if team_id is None: + raise AppwriteException('Missing required parameter: "team_id"') + + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{teamId}', str(self._normalize_value(team_id))) + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('get', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def update_installation( + self, + team_id: str, + installation_id: str, + authorization_details: Optional[str] = None + ) -> AppInstallation: + """ + Update an app installation on a team. Only team members with the owner role can update installations. The installation's granted scopes are refreshed to the scopes the app currently requests; previously issued installation access tokens are revoked. + + Parameters + ---------- + team_id : str + Team ID. + installation_id : str + Installation unique ID. + authorization_details : Optional[str] + Authorization details granted to the installation as a JSON array of objects, each with a `type` and app-defined fields. Omit to keep the current value. + + Returns + ------- + AppInstallation + API response as a typed Pydantic model + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/teams/{teamId}/installations/{installationId}' + api_params = {} + if team_id is None: + raise AppwriteException('Missing required parameter: "team_id"') + + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{teamId}', str(self._normalize_value(team_id))) + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + if authorization_details is not None: + api_params['authorizationDetails'] = self._normalize_value(authorization_details) + + response = self.client.call('put', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return self._parse_response(response, model=AppInstallation) + + + def delete_installation( + self, + team_id: str, + installation_id: str + ) -> Dict[str, Any]: + """ + Uninstall an app from a team by its installation ID. Only team members with the owner role can remove installations. Previously issued installation access tokens are revoked. + + Parameters + ---------- + team_id : str + Team ID. + installation_id : str + Installation unique ID. + + Returns + ------- + Dict[str, Any] + API response as a dictionary + + Raises + ------ + AppwriteException + If API request fails + """ + + api_path = '/teams/{teamId}/installations/{installationId}' + api_params = {} + if team_id is None: + raise AppwriteException('Missing required parameter: "team_id"') + + if installation_id is None: + raise AppwriteException('Missing required parameter: "installation_id"') + + api_path = api_path.replace('{teamId}', str(self._normalize_value(team_id))) + api_path = api_path.replace('{installationId}', str(self._normalize_value(installation_id))) + + + response = self.client.call('delete', api_path, { + 'X-Appwrite-Project': self.client.get_config('project'), + 'content-type': 'application/json', + 'accept': 'application/json', + }, api_params) + + return response + + def list_memberships( self, team_id: str, diff --git a/docs/examples/apps/create-installation-token.md b/docs/examples/apps/create-installation-token.md new file mode 100644 index 00000000..62a62ca6 --- /dev/null +++ b/docs/examples/apps/create-installation-token.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import Oauth2Token + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +apps = Apps(client) + +result: Oauth2Token = apps.create_installation_token( + app_id = '', + installation_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/create-key.md b/docs/examples/apps/create-key.md new file mode 100644 index 00000000..2821ba65 --- /dev/null +++ b/docs/examples/apps/create-key.md @@ -0,0 +1,18 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppKey + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +apps = Apps(client) + +result: AppKey = apps.create_key( + app_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/delete-key.md b/docs/examples/apps/delete-key.md new file mode 100644 index 00000000..dca52e39 --- /dev/null +++ b/docs/examples/apps/delete-key.md @@ -0,0 +1,16 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +apps = Apps(client) + +result = apps.delete_key( + app_id = '', + key_id = '' +) +``` diff --git a/docs/examples/apps/get-installation.md b/docs/examples/apps/get-installation.md new file mode 100644 index 00000000..f6a52721 --- /dev/null +++ b/docs/examples/apps/get-installation.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +apps = Apps(client) + +result: AppInstallation = apps.get_installation( + app_id = '', + installation_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/get-key.md b/docs/examples/apps/get-key.md new file mode 100644 index 00000000..4df3d424 --- /dev/null +++ b/docs/examples/apps/get-key.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppKey + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +apps = Apps(client) + +result: AppKey = apps.get_key( + app_id = '', + key_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/list-installation-scopes.md b/docs/examples/apps/list-installation-scopes.md new file mode 100644 index 00000000..4705cf7b --- /dev/null +++ b/docs/examples/apps/list-installation-scopes.md @@ -0,0 +1,16 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppScopeList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +apps = Apps(client) + +result: AppScopeList = apps.list_installation_scopes() + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/list-installations.md b/docs/examples/apps/list-installations.md new file mode 100644 index 00000000..f5e292b2 --- /dev/null +++ b/docs/examples/apps/list-installations.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppInstallationList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_key('') # Your secret API key + +apps = Apps(client) + +result: AppInstallationList = apps.list_installations( + app_id = '', + queries = [], # optional + total = False # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/list-keys.md b/docs/examples/apps/list-keys.md new file mode 100644 index 00000000..7ed78543 --- /dev/null +++ b/docs/examples/apps/list-keys.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.apps import Apps +from appwrite.models import AppKeyList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +apps = Apps(client) + +result: AppKeyList = apps.list_keys( + app_id = '', + queries = [], # optional + total = False # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/apps/update.md b/docs/examples/apps/update.md index 49c85efb..c9006df6 100644 --- a/docs/examples/apps/update.md +++ b/docs/examples/apps/update.md @@ -28,7 +28,9 @@ result: App = apps.update( redirect_uris = [], # optional post_logout_redirect_uris = [], # optional type = 'public', # optional - device_flow = False # optional + device_flow = False, # optional + installation_scopes = [], # optional + installation_redirect_url = 'https://example.com' # optional ) print(result.model_dump()) diff --git a/docs/examples/organization/create-installation.md b/docs/examples/organization/create-installation.md new file mode 100644 index 00000000..c7af602a --- /dev/null +++ b/docs/examples/organization/create-installation.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.organization import Organization +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +organization = Organization(client) + +result: AppInstallation = organization.create_installation( + app_id = '', + authorization_details = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/organization/delete-installation.md b/docs/examples/organization/delete-installation.md new file mode 100644 index 00000000..82790282 --- /dev/null +++ b/docs/examples/organization/delete-installation.md @@ -0,0 +1,15 @@ +```python +from appwrite.client import Client +from appwrite.services.organization import Organization + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +organization = Organization(client) + +result = organization.delete_installation( + installation_id = '' +) +``` diff --git a/docs/examples/organization/get-installation.md b/docs/examples/organization/get-installation.md new file mode 100644 index 00000000..0bd78a30 --- /dev/null +++ b/docs/examples/organization/get-installation.md @@ -0,0 +1,18 @@ +```python +from appwrite.client import Client +from appwrite.services.organization import Organization +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +organization = Organization(client) + +result: AppInstallation = organization.get_installation( + installation_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/organization/list-installations.md b/docs/examples/organization/list-installations.md new file mode 100644 index 00000000..c2ebe4e8 --- /dev/null +++ b/docs/examples/organization/list-installations.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.organization import Organization +from appwrite.models import AppInstallationList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +organization = Organization(client) + +result: AppInstallationList = organization.list_installations( + queries = [], # optional + total = False # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/organization/update-installation.md b/docs/examples/organization/update-installation.md new file mode 100644 index 00000000..ab8301b2 --- /dev/null +++ b/docs/examples/organization/update-installation.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.organization import Organization +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +organization = Organization(client) + +result: AppInstallation = organization.update_installation( + installation_id = '', + authorization_details = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/project/update-o-auth-2-server.md b/docs/examples/project/update-o-auth-2-server.md index 6be24c54..bce0876e 100644 --- a/docs/examples/project/update-o-auth-2-server.md +++ b/docs/examples/project/update-o-auth-2-server.md @@ -19,6 +19,7 @@ result: ProjectModel = project.update_o_auth2_server( refresh_token_duration = 60, # optional public_access_token_duration = 60, # optional public_refresh_token_duration = 60, # optional + installation_access_token_duration = 60, # optional confidential_pkce = False, # optional verification_url = 'https://example.com', # optional user_code_length = 6, # optional diff --git a/docs/examples/teams/create-installation.md b/docs/examples/teams/create-installation.md new file mode 100644 index 00000000..185e379f --- /dev/null +++ b/docs/examples/teams/create-installation.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.teams import Teams +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +teams = Teams(client) + +result: AppInstallation = teams.create_installation( + team_id = '', + app_id = '', + authorization_details = '' # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/teams/delete-installation.md b/docs/examples/teams/delete-installation.md new file mode 100644 index 00000000..912195d3 --- /dev/null +++ b/docs/examples/teams/delete-installation.md @@ -0,0 +1,16 @@ +```python +from appwrite.client import Client +from appwrite.services.teams import Teams + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +teams = Teams(client) + +result = teams.delete_installation( + team_id = '', + installation_id = '' +) +``` diff --git a/docs/examples/teams/get-installation.md b/docs/examples/teams/get-installation.md new file mode 100644 index 00000000..06ad5f01 --- /dev/null +++ b/docs/examples/teams/get-installation.md @@ -0,0 +1,19 @@ +```python +from appwrite.client import Client +from appwrite.services.teams import Teams +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +teams = Teams(client) + +result: AppInstallation = teams.get_installation( + team_id = '', + installation_id = '' +) + +print(result.model_dump()) +``` diff --git a/docs/examples/teams/list-installations.md b/docs/examples/teams/list-installations.md new file mode 100644 index 00000000..fbc9485b --- /dev/null +++ b/docs/examples/teams/list-installations.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.teams import Teams +from appwrite.models import AppInstallationList + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +teams = Teams(client) + +result: AppInstallationList = teams.list_installations( + team_id = '', + queries = [], # optional + total = False # optional +) + +print(result.model_dump()) +``` diff --git a/docs/examples/teams/update-installation.md b/docs/examples/teams/update-installation.md new file mode 100644 index 00000000..a5df1213 --- /dev/null +++ b/docs/examples/teams/update-installation.md @@ -0,0 +1,20 @@ +```python +from appwrite.client import Client +from appwrite.services.teams import Teams +from appwrite.models import AppInstallation + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint +client.set_project('') # Your project ID +client.set_session('') # The user session to authenticate with + +teams = Teams(client) + +result: AppInstallation = teams.update_installation( + team_id = '', + installation_id = '', + authorization_details = '' # optional +) + +print(result.model_dump()) +``` diff --git a/pyproject.toml b/pyproject.toml index 0d439733..b71d5337 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "appwrite" -version = "22.1.0" +version = "22.2.0" description = "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API" readme = "README.md" requires-python = ">=3.9" diff --git a/setup.py b/setup.py index 1baebf1e..3d51ebff 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name = 'appwrite', packages = setuptools.find_packages(), - version = '22.1.0', + version = '22.2.0', license='BSD-3-Clause', description = 'Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API', long_description = long_description, @@ -18,7 +18,7 @@ maintainer = 'Appwrite Team', maintainer_email = 'team@appwrite.io', url = 'https://appwrite.io/support', - download_url='https://github.com/appwrite/sdk-for-python/archive/22.1.0.tar.gz', + download_url='https://github.com/appwrite/sdk-for-python/archive/22.2.0.tar.gz', install_requires=[ 'requests', 'pydantic>=2,<3', diff --git a/test/services/test_apps.py b/test/services/test_apps.py index e457b572..e923f2e5 100644 --- a/test/services/test_apps.py +++ b/test/services/test_apps.py @@ -53,6 +53,8 @@ def test_create(self, m): "deviceFlow": True, "teamId": "5e5ea5c16897e", "userId": "5e5ea5c16897e", + "installationScopes": [], + "installationRedirectUrl": "https:\/\/example.com\/setup", "secrets": [] } headers = {'Content-Type': 'application/json'} @@ -66,6 +68,20 @@ def test_create(self, m): self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() + def test_list_installation_scopes(self, m): + data = { + "total": 5.0, + "scopes": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.list_installation_scopes( + ) + + self.assertEqual(response.to_dict(), data) + @requests_mock.Mocker() def test_list_o_auth2_scopes(self, m): data = { @@ -106,6 +122,8 @@ def test_get(self, m): "deviceFlow": True, "teamId": "5e5ea5c16897e", "userId": "5e5ea5c16897e", + "installationScopes": [], + "installationRedirectUrl": "https:\/\/example.com\/setup", "secrets": [] } headers = {'Content-Type': 'application/json'} @@ -143,6 +161,8 @@ def test_update(self, m): "deviceFlow": True, "teamId": "5e5ea5c16897e", "userId": "5e5ea5c16897e", + "installationScopes": [], + "installationRedirectUrl": "https:\/\/example.com\/setup", "secrets": [] } headers = {'Content-Type': 'application/json'} @@ -167,6 +187,134 @@ def test_delete(self, m): self.assertEqual(response, data) + @requests_mock.Mocker() + def test_list_installations(self, m): + data = { + "total": 5.0, + "installations": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.list_installations( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.get_installation( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_create_installation_token(self, m): + data = { + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...", + "token_type": "Bearer", + "expires_in": 3600.0, + "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...", + "scope": "openid email profile" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.create_installation_token( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_list_keys(self, m): + data = { + "total": 5.0, + "keys": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.list_keys( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_create_key(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "secret": "5f3c8d2a1b9e4f7a6c8b2d1e9f4a7b3c5d8e1f2a9b4c7d6e3f5a8b1c4d7e2f9a", + "hint": "f5c6c7", + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.create_key( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_key(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "secret": "5f3c8d2a1b9e4f7a6c8b2d1e9f4a7b3c5d8e1f2a9b4c7d6e3f5a8b1c4d7e2f9a", + "hint": "f5c6c7", + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.get_key( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_delete_key(self, m): + data = '' + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.apps.delete_key( + '', + '', + ) + + self.assertEqual(response, data) + @requests_mock.Mocker() def test_update_labels(self, m): data = { @@ -193,6 +341,8 @@ def test_update_labels(self, m): "deviceFlow": True, "teamId": "5e5ea5c16897e", "userId": "5e5ea5c16897e", + "installationScopes": [], + "installationRedirectUrl": "https:\/\/example.com\/setup", "secrets": [] } headers = {'Content-Type': 'application/json'} @@ -302,6 +452,8 @@ def test_update_team(self, m): "deviceFlow": True, "teamId": "5e5ea5c16897e", "userId": "5e5ea5c16897e", + "installationScopes": [], + "installationRedirectUrl": "https:\/\/example.com\/setup", "secrets": [] } headers = {'Content-Type': 'application/json'} diff --git a/test/services/test_organization.py b/test/services/test_organization.py index 399dd374..544d647d 100644 --- a/test/services/test_organization.py +++ b/test/services/test_organization.py @@ -477,6 +477,98 @@ def test_delete(self, m): self.assertEqual(response, data) + @requests_mock.Mocker() + def test_list_installations(self, m): + data = { + "total": 5.0, + "installations": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.list_installations( + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_create_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.create_installation( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.get_installation( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_update_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.update_installation( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_delete_installation(self, m): + data = '' + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.organization.delete_installation( + '', + ) + + self.assertEqual(response, data) + @requests_mock.Mocker() def test_list_keys(self, m): data = { diff --git a/test/services/test_teams.py b/test/services/test_teams.py index 84688663..684d00f8 100644 --- a/test/services/test_teams.py +++ b/test/services/test_teams.py @@ -98,6 +98,103 @@ def test_delete(self, m): self.assertEqual(response, data) + @requests_mock.Mocker() + def test_list_installations(self, m): + data = { + "total": 5.0, + "installations": [] +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.teams.list_installations( + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_create_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.teams.create_installation( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_get_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.teams.get_installation( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_update_installation(self, m): + data = { + "$id": "5e5ea5c16897e", + "$createdAt": "2020-10-15T06:38:00.000+00:00", + "$updatedAt": "2020-10-15T06:38:00.000+00:00", + "appId": "5e5ea5c16897e", + "teamId": "5e5ea5c16897e", + "scopes": [], + "authorizationDetails": {}, + "createdById": "5e5ea5c16897e", + "createdByName": "Walter White" +} + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.teams.update_installation( + '', + '', + ) + + self.assertEqual(response.to_dict(), data) + + @requests_mock.Mocker() + def test_delete_installation(self, m): + data = '' + headers = {'Content-Type': 'application/json'} + m.request(requests_mock.ANY, requests_mock.ANY, text=json.dumps(data), headers=headers) + + response = self.teams.delete_installation( + '', + '', + ) + + self.assertEqual(response, data) + @requests_mock.Mocker() def test_list_memberships(self, m): data = {