From 50c29a337021972d8768ae976ed28ad1d800b2ac Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 15:48:57 +0000 Subject: [PATCH 1/9] feat(generated): Pipes (batch 8ad8e447) (#723) Co-authored-by: workos-sdk-automation[bot] <255426317+workos-sdk-automation[bot]@users.noreply.github.com> --- ...110a04e6629f048799bbf753bddc6c5f4d10c0f.md | 18 ++++ .last-synced-sha | 2 +- src/workos/pipes/_resource.py | 64 ++++++++++++- ...ata_integrations_get_user_token_request.py | 5 + .../models/data_integrations_list_response.py | 2 +- .../data_integrations_list_response_data.py | 11 +++ ...a_integrations_vend_credentials_request.py | 5 + ...a_integrations_get_user_token_request.json | 3 +- .../data_integrations_list_response.json | 25 ++++- .../data_integrations_list_response_data.json | 25 ++++- ...integrations_vend_credentials_request.json | 3 +- tests/test_pipes.py | 54 +++++++++-- tests/test_pipes_models_round_trip.py | 93 +++++++++++++++++++ 13 files changed, 296 insertions(+), 14 deletions(-) create mode 100644 .changelog-pending/2026-09-05T15-36-39-b110a04e6629f048799bbf753bddc6c5f4d10c0f.md diff --git a/.changelog-pending/2026-09-05T15-36-39-b110a04e6629f048799bbf753bddc6c5f4d10c0f.md b/.changelog-pending/2026-09-05T15-36-39-b110a04e6629f048799bbf753bddc6c5f4d10c0f.md new file mode 100644 index 000000000..d39ec0d69 --- /dev/null +++ b/.changelog-pending/2026-09-05T15-36-39-b110a04e6629f048799bbf753bddc6c5f4d10c0f.md @@ -0,0 +1,18 @@ +* [#723](https://github.com/workos/workos-python/pull/723) fix(generated): regenerate from spec + + **Features** + * **[pipes](https://workos.com/docs/reference/pipes)**: + * Added `connected_account_id` to `DataIntegrationsVendCredentialsRequest` + * Added `connected_account_id` to `DataIntegrationsGetUserTokenRequest` + * Added parameter `UserManagementDataProviders.getUserDataInstallation.connected_account_id` + * Changed errors for endpoint `GET /user_management/users/{user_id}/connected_accounts/{slug}` + * Added parameter `UserManagementDataProviders.updateUserDataInstallation.connected_account_id` + * Changed errors for endpoint `PUT /user_management/users/{user_id}/connected_accounts/{slug}` + * Added parameter `UserManagementDataProviders.deleteUserDataInstallation.connected_account_id` + * Changed errors for endpoint `DELETE /user_management/users/{user_id}/connected_accounts/{slug}` + * Added `connected_accounts` to `DataIntegrationsListResponseData` + * Changed errors for endpoint `PUT /data-integrations/{slug}` + * Changed errors for endpoint `PUT /data-integrations/{slug}/api-key` + * Changed errors for endpoint `PUT /data-integrations/{slug}/client-credentials` + * Changed errors for endpoint `POST /data-integrations/{slug}/credentials` + * Changed errors for endpoint `POST /data-integrations/{provider}/token` diff --git a/.last-synced-sha b/.last-synced-sha index d20a1099f..2f412fd37 100644 --- a/.last-synced-sha +++ b/.last-synced-sha @@ -1 +1 @@ -edb560e2be3f54b668ea8d11fa5a060c87ab5087 +6fba233c50a5b651b2df7b859b75bfe715bc825a diff --git a/src/workos/pipes/_resource.py b/src/workos/pipes/_resource.py index 4e2ea734f..950eb8c87 100644 --- a/src/workos/pipes/_resource.py +++ b/src/workos/pipes/_resource.py @@ -219,6 +219,7 @@ def update_data_integration( AuthenticationError: If the API key is invalid (401). AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. @@ -303,6 +304,7 @@ def update_data_integration_api_key( AuthenticationError: If the API key is invalid (401). AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. @@ -407,6 +409,7 @@ def update_data_integration_client_credentials( AuthenticationError: If the API key is invalid (401). AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. @@ -436,6 +439,7 @@ def create_data_integration_credential( *, user_id: str, organization_id: str | None = None, + connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> DataIntegrationCredentialsResponse: """Vend credentials for a connected account @@ -446,6 +450,7 @@ def create_data_integration_credential( slug: The identifier of the integration. user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -455,6 +460,7 @@ def create_data_integration_credential( BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. @@ -464,6 +470,7 @@ def create_data_integration_credential( for k, v in { "user_id": user_id, "organization_id": organization_id, + "connected_account_id": connected_account_id, }.items() if v is not None } @@ -481,6 +488,7 @@ def get_access_token( *, user_id: str, organization_id: str | None | NotGiven = NOT_GIVEN, + connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> DataIntegrationAccessTokenResponse: """Get an access token for a connected account @@ -491,6 +499,7 @@ def get_access_token( provider: The identifier of the integration. user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -500,12 +509,18 @@ def get_access_token( BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ body: dict[str, Any] = { - "user_id": user_id, + k: v + for k, v in { + "user_id": user_id, + "connected_account_id": connected_account_id, + }.items() + if v is not None } if not isinstance(organization_id, NotGiven): body["organization_id"] = organization_id @@ -523,6 +538,7 @@ def get_user_connected_account( slug: str, *, organization_id: str | None = None, + connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> ConnectedAccount: """Get a connected account @@ -533,14 +549,17 @@ def get_user_connected_account( user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: ConnectedAccount Raises: + BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ @@ -548,6 +567,7 @@ def get_user_connected_account( k: v for k, v in { "organization_id": organization_id, + "connected_account_id": connected_account_id, }.items() if v is not None } @@ -648,6 +668,7 @@ def update_user_connected_account( scopes: list[str] | None = None, state: ConnectedAccountInputState | str | None = None, organization_id: str | None = None, + connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> ConnectedAccount: """Update a connected account @@ -663,14 +684,17 @@ def update_user_connected_account( scopes: The OAuth scopes granted for this connection. state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to update. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: ConnectedAccount Raises: + BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ @@ -689,6 +713,7 @@ def update_user_connected_account( k: v for k, v in { "organization_id": organization_id, + "connected_account_id": connected_account_id, }.items() if v is not None } @@ -713,6 +738,7 @@ def delete_user_connected_account( slug: str, *, organization_id: str | None = None, + connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> None: """Delete a connected account @@ -723,11 +749,14 @@ def delete_user_connected_account( user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to delete. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Raises: + BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ @@ -735,6 +764,7 @@ def delete_user_connected_account( k: v for k, v in { "organization_id": organization_id, + "connected_account_id": connected_account_id, }.items() if v is not None } @@ -980,6 +1010,7 @@ async def update_data_integration( AuthenticationError: If the API key is invalid (401). AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. @@ -1064,6 +1095,7 @@ async def update_data_integration_api_key( AuthenticationError: If the API key is invalid (401). AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. @@ -1168,6 +1200,7 @@ async def update_data_integration_client_credentials( AuthenticationError: If the API key is invalid (401). AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. @@ -1197,6 +1230,7 @@ async def create_data_integration_credential( *, user_id: str, organization_id: str | None = None, + connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> DataIntegrationCredentialsResponse: """Vend credentials for a connected account @@ -1207,6 +1241,7 @@ async def create_data_integration_credential( slug: The identifier of the integration. user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -1216,6 +1251,7 @@ async def create_data_integration_credential( BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. @@ -1225,6 +1261,7 @@ async def create_data_integration_credential( for k, v in { "user_id": user_id, "organization_id": organization_id, + "connected_account_id": connected_account_id, }.items() if v is not None } @@ -1242,6 +1279,7 @@ async def get_access_token( *, user_id: str, organization_id: str | None | NotGiven = NOT_GIVEN, + connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> DataIntegrationAccessTokenResponse: """Get an access token for a connected account @@ -1252,6 +1290,7 @@ async def get_access_token( provider: The identifier of the integration. user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -1261,12 +1300,18 @@ async def get_access_token( BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ body: dict[str, Any] = { - "user_id": user_id, + k: v + for k, v in { + "user_id": user_id, + "connected_account_id": connected_account_id, + }.items() + if v is not None } if not isinstance(organization_id, NotGiven): body["organization_id"] = organization_id @@ -1284,6 +1329,7 @@ async def get_user_connected_account( slug: str, *, organization_id: str | None = None, + connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> ConnectedAccount: """Get a connected account @@ -1294,14 +1340,17 @@ async def get_user_connected_account( user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: ConnectedAccount Raises: + BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ @@ -1309,6 +1358,7 @@ async def get_user_connected_account( k: v for k, v in { "organization_id": organization_id, + "connected_account_id": connected_account_id, }.items() if v is not None } @@ -1409,6 +1459,7 @@ async def update_user_connected_account( scopes: list[str] | None = None, state: ConnectedAccountInputState | str | None = None, organization_id: str | None = None, + connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> ConnectedAccount: """Update a connected account @@ -1424,14 +1475,17 @@ async def update_user_connected_account( scopes: The OAuth scopes granted for this connection. state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to update. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: ConnectedAccount Raises: + BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ @@ -1450,6 +1504,7 @@ async def update_user_connected_account( k: v for k, v in { "organization_id": organization_id, + "connected_account_id": connected_account_id, }.items() if v is not None } @@ -1474,6 +1529,7 @@ async def delete_user_connected_account( slug: str, *, organization_id: str | None = None, + connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> None: """Delete a connected account @@ -1484,11 +1540,14 @@ async def delete_user_connected_account( user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to delete. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Raises: + BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ @@ -1496,6 +1555,7 @@ async def delete_user_connected_account( k: v for k, v in { "organization_id": organization_id, + "connected_account_id": connected_account_id, }.items() if v is not None } diff --git a/src/workos/pipes/models/data_integrations_get_user_token_request.py b/src/workos/pipes/models/data_integrations_get_user_token_request.py index 1fc0ef92e..7c93c3b19 100644 --- a/src/workos/pipes/models/data_integrations_get_user_token_request.py +++ b/src/workos/pipes/models/data_integrations_get_user_token_request.py @@ -16,6 +16,8 @@ class DataIntegrationsGetUserTokenRequest: """A [User](https://workos.com/docs/reference/authkit/user) identifier.""" organization_id: str | None = None """An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization.""" + connected_account_id: str | None = None + """A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsGetUserTokenRequest: @@ -24,6 +26,7 @@ def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsGetUserTokenRequest: return cls( user_id=data["user_id"], organization_id=data.get("organization_id"), + connected_account_id=data.get("connected_account_id"), ) except (KeyError, ValueError) as e: _raise_deserialize_error("DataIntegrationsGetUserTokenRequest", e) @@ -36,4 +39,6 @@ def to_dict(self) -> dict[str, Any]: result["organization_id"] = self.organization_id else: result["organization_id"] = None + if self.connected_account_id is not None: + result["connected_account_id"] = self.connected_account_id return result diff --git a/src/workos/pipes/models/data_integrations_list_response.py b/src/workos/pipes/models/data_integrations_list_response.py index 4aa62cb75..a8482582a 100644 --- a/src/workos/pipes/models/data_integrations_list_response.py +++ b/src/workos/pipes/models/data_integrations_list_response.py @@ -17,7 +17,7 @@ class DataIntegrationsListResponse: object: Literal["list"] """Indicates this is a list response.""" data: list[DataIntegrationsListResponseData] - """A list of [providers](https://workos.com/docs/reference/pipes/provider), each including a [`connected_account`](https://workos.com/docs/reference/pipes/connected-account) field with the user's connection status.""" + """A list of [providers](https://workos.com/docs/reference/pipes/provider), each including the legacy `connected_account` field and the additive `connected_accounts` collection.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsListResponse: diff --git a/src/workos/pipes/models/data_integrations_list_response_data.py b/src/workos/pipes/models/data_integrations_list_response_data.py index a4f173e01..d4cbac8e9 100644 --- a/src/workos/pipes/models/data_integrations_list_response_data.py +++ b/src/workos/pipes/models/data_integrations_list_response_data.py @@ -47,6 +47,8 @@ class DataIntegrationsListResponseData: """The timestamp when the provider was last updated.""" connected_account: DataIntegrationsListResponseDataConnectedAccount | None """The user's [connected account](https://workos.com/docs/reference/pipes/connected-account) for this provider, or `null` if the user has not connected.""" + connected_accounts: list[DataIntegrationsListResponseDataConnectedAccount] + """The user's connected accounts for this provider in the requested ownership context.""" auth_methods: list[DataIntegrationsListResponseDataAuthMethods] | None = None """The authentication methods supported by this provider (`oauth`, `api_key`, `client_credentials`, or a combination). Defaults to `["oauth"]` if absent.""" @@ -71,6 +73,12 @@ def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsListResponseData: ) if (_v_connected_account := data["connected_account"]) is not None else None, + connected_accounts=[ + DataIntegrationsListResponseDataConnectedAccount.from_dict( + cast(dict[str, Any], item) + ) + for item in cast(list[Any], data["connected_accounts"]) + ], auth_methods=[ DataIntegrationsListResponseDataAuthMethods(item) for item in cast(list[Any], _v_auth_methods) @@ -107,6 +115,9 @@ def to_dict(self) -> dict[str, Any]: result["connected_account"] = self.connected_account.to_dict() else: result["connected_account"] = None + result["connected_accounts"] = [ + item.to_dict() for item in self.connected_accounts + ] if self.auth_methods is not None: result["auth_methods"] = [ item.value if isinstance(item, Enum) else item diff --git a/src/workos/pipes/models/data_integrations_vend_credentials_request.py b/src/workos/pipes/models/data_integrations_vend_credentials_request.py index 703af7f10..941de6e35 100644 --- a/src/workos/pipes/models/data_integrations_vend_credentials_request.py +++ b/src/workos/pipes/models/data_integrations_vend_credentials_request.py @@ -16,6 +16,8 @@ class DataIntegrationsVendCredentialsRequest: """A [User](https://workos.com/docs/reference/authkit/user) identifier.""" organization_id: str | None = None """An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization.""" + connected_account_id: str | None = None + """A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsVendCredentialsRequest: @@ -24,6 +26,7 @@ def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsVendCredentialsReque return cls( user_id=data["user_id"], organization_id=data.get("organization_id"), + connected_account_id=data.get("connected_account_id"), ) except (KeyError, ValueError) as e: _raise_deserialize_error("DataIntegrationsVendCredentialsRequest", e) @@ -34,4 +37,6 @@ def to_dict(self) -> dict[str, Any]: result["user_id"] = self.user_id if self.organization_id is not None: result["organization_id"] = self.organization_id + if self.connected_account_id is not None: + result["connected_account_id"] = self.connected_account_id return result diff --git a/tests/fixtures/data_integrations_get_user_token_request.json b/tests/fixtures/data_integrations_get_user_token_request.json index ef8c44380..66f0412ec 100644 --- a/tests/fixtures/data_integrations_get_user_token_request.json +++ b/tests/fixtures/data_integrations_get_user_token_request.json @@ -1,4 +1,5 @@ { "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT" + "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT", + "connected_account_id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" } diff --git a/tests/fixtures/data_integrations_list_response.json b/tests/fixtures/data_integrations_list_response.json index 00951fba1..365f599fc 100644 --- a/tests/fixtures/data_integrations_list_response.json +++ b/tests/fixtures/data_integrations_list_response.json @@ -39,7 +39,30 @@ "created_at": "2024-01-16T14:20:00.000Z", "updated_at": "2024-01-16T14:20:00.000Z", "userlandUserId": "test_userlandUserId" - } + }, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": null, + "scopes": [ + "repo", + "user:email" + ], + "auth_method": "oauth", + "api_key_last_4": null, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": { + "instance_url": "https://example.my.salesforce.com" + }, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId" + } + ] } ] } diff --git a/tests/fixtures/data_integrations_list_response_data.json b/tests/fixtures/data_integrations_list_response_data.json index ccb24ea7b..f3997ed18 100644 --- a/tests/fixtures/data_integrations_list_response_data.json +++ b/tests/fixtures/data_integrations_list_response_data.json @@ -36,5 +36,28 @@ "created_at": "2024-01-16T14:20:00.000Z", "updated_at": "2024-01-16T14:20:00.000Z", "userlandUserId": "test_userlandUserId" - } + }, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": null, + "scopes": [ + "repo", + "user:email" + ], + "auth_method": "oauth", + "api_key_last_4": null, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": { + "instance_url": "https://example.my.salesforce.com" + }, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId" + } + ] } diff --git a/tests/fixtures/data_integrations_vend_credentials_request.json b/tests/fixtures/data_integrations_vend_credentials_request.json index ef8c44380..66f0412ec 100644 --- a/tests/fixtures/data_integrations_vend_credentials_request.json +++ b/tests/fixtures/data_integrations_vend_credentials_request.json @@ -1,4 +1,5 @@ { "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT" + "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT", + "connected_account_id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" } diff --git a/tests/test_pipes.py b/tests/test_pipes.py index b05bc08e3..f2b194941 100644 --- a/tests/test_pipes.py +++ b/tests/test_pipes.py @@ -209,10 +209,17 @@ def test_get_user_connected_account(self, workos, httpx_mock): def test_get_user_connected_account_encodes_query_params(self, workos, httpx_mock): httpx_mock.add_response(json=load_fixture("connected_account.json")) workos.pipes.get_user_connected_account( - "test_user_id", "test_slug", organization_id="value organization_id/test" + "test_user_id", + "test_slug", + organization_id="value organization_id/test", + connected_account_id="value connected_account_id/test", ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) def test_create_user_connected_account(self, workos, httpx_mock): httpx_mock.add_response( @@ -257,10 +264,17 @@ def test_update_user_connected_account_encodes_query_params( ): httpx_mock.add_response(json=load_fixture("connected_account.json")) workos.pipes.update_user_connected_account( - "test_user_id", "test_slug", organization_id="value organization_id/test" + "test_user_id", + "test_slug", + organization_id="value organization_id/test", + connected_account_id="value connected_account_id/test", ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) def test_delete_user_connected_account(self, workos, httpx_mock): httpx_mock.add_response(status_code=204) @@ -277,10 +291,17 @@ def test_delete_user_connected_account_encodes_query_params( ): httpx_mock.add_response(status_code=204) workos.pipes.delete_user_connected_account( - "test_user_id", "test_slug", organization_id="value organization_id/test" + "test_user_id", + "test_slug", + organization_id="value organization_id/test", + connected_account_id="value connected_account_id/test", ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) def test_list_user_data_providers(self, workos, httpx_mock): httpx_mock.add_response( @@ -559,10 +580,17 @@ async def test_get_user_connected_account_encodes_query_params( ): httpx_mock.add_response(json=load_fixture("connected_account.json")) await async_workos.pipes.get_user_connected_account( - "test_user_id", "test_slug", organization_id="value organization_id/test" + "test_user_id", + "test_slug", + organization_id="value organization_id/test", + connected_account_id="value connected_account_id/test", ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) @pytest.mark.asyncio async def test_create_user_connected_account(self, async_workos, httpx_mock): @@ -611,10 +639,17 @@ async def test_update_user_connected_account_encodes_query_params( ): httpx_mock.add_response(json=load_fixture("connected_account.json")) await async_workos.pipes.update_user_connected_account( - "test_user_id", "test_slug", organization_id="value organization_id/test" + "test_user_id", + "test_slug", + organization_id="value organization_id/test", + connected_account_id="value connected_account_id/test", ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) @pytest.mark.asyncio async def test_delete_user_connected_account(self, async_workos, httpx_mock): @@ -635,10 +670,17 @@ async def test_delete_user_connected_account_encodes_query_params( ): httpx_mock.add_response(status_code=204) await async_workos.pipes.delete_user_connected_account( - "test_user_id", "test_slug", organization_id="value organization_id/test" + "test_user_id", + "test_slug", + organization_id="value organization_id/test", + connected_account_id="value connected_account_id/test", ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) @pytest.mark.asyncio async def test_list_user_data_providers(self, async_workos, httpx_mock): diff --git a/tests/test_pipes_models_round_trip.py b/tests/test_pipes_models_round_trip.py index 23055c51b..be400f2ab 100644 --- a/tests/test_pipes_models_round_trip.py +++ b/tests/test_pipes_models_round_trip.py @@ -556,6 +556,26 @@ def test_data_integrations_list_response_minimal_payload(self): "updated_at": "2024-01-16T14:20:00.000Z", "userlandUserId": "test_userlandUserId", }, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": { + "instance_url": "https://example.my.salesforce.com" + }, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], } ], } @@ -586,6 +606,24 @@ def test_data_integrations_list_response_data_minimal_payload(self): "created_at": "2024-01-15T10:30:00.000Z", "updated_at": "2024-01-15T10:30:00.000Z", "connected_account": None, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], } instance = DataIntegrationsListResponseData.from_dict(data) serialized = instance.to_dict() @@ -601,6 +639,7 @@ def test_data_integrations_list_response_data_minimal_payload(self): assert serialized["created_at"] == data["created_at"] assert serialized["updated_at"] == data["updated_at"] assert serialized["connected_account"] == data["connected_account"] + assert serialized["connected_accounts"] == data["connected_accounts"] def test_data_integrations_list_response_data_omits_absent_optional_non_nullable_fields( self, @@ -633,6 +672,24 @@ def test_data_integrations_list_response_data_omits_absent_optional_non_nullable "updated_at": "2024-01-16T14:20:00.000Z", "userlandUserId": "test_userlandUserId", }, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], } instance = DataIntegrationsListResponseData.from_dict(data) serialized = instance.to_dict() @@ -653,6 +710,24 @@ def test_data_integrations_list_response_data_preserves_nullable_fields(self): "created_at": "2024-01-15T10:30:00.000Z", "updated_at": "2024-01-15T10:30:00.000Z", "connected_account": None, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], } instance = DataIntegrationsListResponseData.from_dict(data) serialized = instance.to_dict() @@ -690,6 +765,24 @@ def test_data_integrations_list_response_data_round_trips_unknown_enum_values(se "updated_at": "2024-01-16T14:20:00.000Z", "userlandUserId": "test_userlandUserId", }, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], } instance = DataIntegrationsListResponseData.from_dict(data) assert instance.to_dict() == data From 480c1c87aefbf9397fb34f6aaa9da3c5a653e672 Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:04:53 +0000 Subject: [PATCH 2/9] feat(generated): Changes to agents, pipes, and user_management (#727) Co-authored-by: workos-sdk-automation[bot] <255426317+workos-sdk-automation[bot]@users.noreply.github.com> --- ...b4d419df1198eced531b9867bfbfdee80433fe2.md | 102 ++++ .last-synced-sha | 2 +- .oagen-manifest.json | 38 ++ src/workos/admin_portal/_resource.py | 8 +- .../admin_portal/models/generate_link.py | 4 +- src/workos/agents/_resource.py | 6 + .../api_keys/models/validate_api_key.py | 33 +- src/workos/common/__init__.py | 15 + src/workos/common/models/__init__.py | 21 + .../account_selection_required_error.py | 36 ++ .../create_data_integration_ownership.py | 27 ++ .../models/data_integration_ownership.py | 8 + ...get_user_token_request_connection_owner.py | 10 + ...upsert_api_key_request_connection_owner.py | 10 + ...nt_credentials_request_connection_owner.py | 10 + ...nd_credentials_request_connection_owner.py | 10 + ...rce_export_completed_data_resource_type.py | 3 +- .../models/audit_logs_retention.py | 2 +- src/workos/pipes/_resource.py | 440 ++++++++++++++++-- src/workos/pipes/models/__init__.py | 1 + .../pipes/models/api_key_installation.py | 42 +- .../pipes/models/create_data_integration.py | 14 + src/workos/pipes/models/data_integration.py | 7 + .../models/data_integration_installation.py | 11 +- ...ata_integrations_get_user_token_request.py | 26 +- .../data_integrations_list_response_data.py | 4 +- ...ata_integrations_upsert_api_key_request.py | 24 +- ...tions_upsert_client_credentials_request.py | 26 +- ...a_integrations_vend_credentials_request.py | 28 +- src/workos/pipes/models/pipes_ownership.py | 21 + src/workos/user_management/_resource.py | 227 +++++++++ src/workos/user_management/models/__init__.py | 4 + .../models/authkit_oauth_resource.py | 52 +++ .../models/create_authkit_oauth_resource.py | 37 ++ .../account_selection_required_error.json | 4 + tests/fixtures/authkit_oauth_resource.json | 8 + .../create_authkit_oauth_resource.json | 4 + tests/fixtures/create_data_integration.json | 1 + tests/fixtures/data_integration.json | 1 + ...a_integrations_get_user_token_request.json | 4 +- ...a_integrations_upsert_api_key_request.json | 2 + ...ons_upsert_client_credentials_request.json | 2 + ...integrations_vend_credentials_request.json | 4 +- .../fixtures/list_authkit_oauth_resource.json | 16 + tests/fixtures/list_data_integration.json | 1 + tests/test_agents.py | 4 + tests/test_common_models_round_trip.py | 19 + tests/test_pipes.py | 96 +++- tests/test_pipes_models_round_trip.py | 11 +- tests/test_user_management.py | 116 +++++ .../test_user_management_models_round_trip.py | 27 ++ 51 files changed, 1563 insertions(+), 66 deletions(-) create mode 100644 .changelog-pending/2026-09-14T13-28-03-0b4d419df1198eced531b9867bfbfdee80433fe2.md create mode 100644 src/workos/common/models/account_selection_required_error.py create mode 100644 src/workos/common/models/create_data_integration_ownership.py create mode 100644 src/workos/common/models/data_integration_ownership.py create mode 100644 src/workos/common/models/data_integrations_get_user_token_request_connection_owner.py create mode 100644 src/workos/common/models/data_integrations_upsert_api_key_request_connection_owner.py create mode 100644 src/workos/common/models/data_integrations_upsert_client_credentials_request_connection_owner.py create mode 100644 src/workos/common/models/data_integrations_vend_credentials_request_connection_owner.py create mode 100644 src/workos/pipes/models/pipes_ownership.py create mode 100644 src/workos/user_management/models/authkit_oauth_resource.py create mode 100644 src/workos/user_management/models/create_authkit_oauth_resource.py create mode 100644 tests/fixtures/account_selection_required_error.json create mode 100644 tests/fixtures/authkit_oauth_resource.json create mode 100644 tests/fixtures/create_authkit_oauth_resource.json create mode 100644 tests/fixtures/list_authkit_oauth_resource.json diff --git a/.changelog-pending/2026-09-14T13-28-03-0b4d419df1198eced531b9867bfbfdee80433fe2.md b/.changelog-pending/2026-09-14T13-28-03-0b4d419df1198eced531b9867bfbfdee80433fe2.md new file mode 100644 index 000000000..9b44e4497 --- /dev/null +++ b/.changelog-pending/2026-09-14T13-28-03-0b4d419df1198eced531b9867bfbfdee80433fe2.md @@ -0,0 +1,102 @@ +* [#727](https://github.com/workos/workos-python/pull/727) feat(generated): regenerate from spec (4 changes) + + **Features** + * **[admin_portal](https://workos.com/docs/reference/admin-portal)**: + * Changed the format of `PortalLinkResponse.link` + * **[agents](https://workos.com/docs/reference/agents)**: + * Added parameter `AgentsSessions.list.organization_id` + * Changed the format of `ClaimViewResponse.user_code` + * Changed the format of `AgentAdminValidateCredentialRequest.credential` + * **[api_keys](https://workos.com/docs/reference/authkit/api-keys)**: + * Changed the format of `ValidateApiKey.value` + * Changed the format of `OrganizationApiKeyWithValue.value` + * Changed the format of `UserApiKeyWithValue.value` + * **[multi_factor_auth](https://workos.com/docs/reference/authkit/mfa)**: + * Changed the format of `AuthenticationChallenge.code` + * Changed the format of `AuthenticationFactorEnrolledTotp.secret` + * Changed the format of `AuthenticationFactorEnrolledTotp.qr_code` + * Changed the format of `AuthenticationFactorEnrolledTotp.uri` + * Changed the format of `AuthenticationChallengesVerifyRequest.code` + * **[pipes](https://workos.com/docs/reference/pipes)**: + * Added `supports_multiple_connections` to `DataIntegrationsVendCredentialsRequest` + * Added `supports_multiple_connections` to `DataIntegrationsGetUserTokenRequest` + * Added parameter `UserManagementDataProviders.getUserDataInstallation.supports_multiple_connections` + * Added parameter `UserManagementDataProviders.updateUserDataInstallation.supports_multiple_connections` + * Added parameter `UserManagementDataProviders.deleteUserDataInstallation.supports_multiple_connections` + * Added parameter `UserManagementDataProviders.getUserDataIntegrations.supports_multiple_connections` + * Added enum `PipesOwnership` + * Added `user` to `CreateDataIntegrationOwnership` + * Added `user` to `DataIntegrationOwnership` + * Added endpoint `GET /data-integrations/{slug}/organization` + * Added endpoint `PUT /data-integrations/{slug}/organization` + * Added endpoint `DELETE /data-integrations/{slug}/organization` + * Added parameter `Pipes.listDataIntegrations.ownership` + * Changed errors for endpoint `GET /data-integrations` + * Changed errors for endpoint `PUT /user_management/users/{user_id}/connected_accounts/{slug}` + * Changed errors for endpoint `DELETE /user_management/users/{user_id}/connected_accounts/{slug}` + * Changed the format of `DataIntegrationCredentialsResponseCredential.value` + * Added `connection_owner` to `DataIntegrationsUpsertApiKeyRequest` + * Added `connection_owner` to `DataIntegrationsUpsertClientCredentialsRequest` + * Added enum `DataIntegrationsUpsertApiKeyRequestConnectionOwner` + * Added enum `DataIntegrationsUpsertClientCredentialsRequestConnectionOwner` + * Added model `AccountSelectionRequiredError` + * Added `connected_account_id` to `DataIntegrationsUpsertApiKeyRequest` + * Added `connected_account_id` to `DataIntegrationsUpsertClientCredentialsRequest` + * Added `ownership` to `CreateDataIntegration` + * Added `ownership` to `DataIntegration` + * Added `connection_owner` to `DataIntegrationsVendCredentialsRequest` + * Added `connection_owner` to `DataIntegrationsGetUserTokenRequest` + * Added enum `CreateDataIntegrationOwnership` + * Added enum `DataIntegrationOwnership` + * Added enum `DataIntegrationsVendCredentialsRequestConnectionOwner` + * Added enum `DataIntegrationsGetUserTokenRequestConnectionOwner` + * Changed errors for endpoint `POST /data-integrations/{slug}/credentials` + * Changed errors for endpoint `POST /data-integrations/{provider}/token` + * **[sso](https://workos.com/docs/reference/sso)**: + * Changed the format of `CreateConnectionKeyPair.key` + * Changed the format of `TokenQuery.code` + * Changed the format of `SSOLogoutAuthorizeResponse.logout_url` + * **[user_management](https://workos.com/docs/reference/authkit/user)**: + * Added model `CreateAuthkitOAuthResource` + * Added model `AuthkitOAuthResource` + * Added service `UserManagementAuthkitOAuthResources` + * Changed the format of `DeviceAuthorizationResponse.verification_uri_complete` + * Changed the format of `VerifyEmailAddress.code` + * Changed the format of `ConfirmEmailChange.code` + * Changed the format of `MagicAuth.code` + * Changed the format of `UserInvite.accept_invitation_url` + * Changed the format of `EmailVerification.code` + * Changed the format of `PasswordReset.password_reset_url` + * Changed the format of `AuthenticateResponse.authkit_authorization_code` + * Changed the format of `DeviceAuthorizationResponse.device_code` + * Changed the format of `DeviceAuthorizationResponse.user_code` + * Changed the format of `AuthorizationCodeSessionAuthenticateRequest.code` + * Changed the format of `AuthorizationCodeSessionAuthenticateRequest.code_verifier` + * Changed the format of `MagicAuthCodeSessionAuthenticateRequest.code` + * Changed the format of `EmailVerificationCodeSessionAuthenticateRequest.code` + * Changed the format of `MfaTotpSessionAuthenticateRequest.code` + * Changed the format of `RadarEmailChallengeCodeSessionAuthenticateRequest.code` + * Changed the format of `RadarSmsChallengeCodeSessionAuthenticateRequest.code` + * Changed the format of `DeviceCodeSessionAuthenticateRequest.device_code` + * Changed the format of `Invitation.accept_invitation_url` + * Changed the format of `MagicAuthSendMagicAuthCodeAndReturnResponse.code` + * **[radar](https://workos.com/docs/reference/radar)**: + * Changed the format of `RadarChallenge.code` + * **[vault](https://workos.com/docs/reference/vault)**: + * Changed the format of `CreateObjectRequest.value` + * Changed the format of `VaultObject.value` + * Changed the format of `UpdateObjectRequest.value` + + **Fixes** + * **[pipes](https://workos.com/docs/reference/pipes)**: + * Removed `userland_user` from `CreateDataIntegrationOwnership` + * Removed `userland_user` from `DataIntegrationOwnership` + * Changed errors for endpoint `POST /user_management/users/{user_id}/connected_accounts/{slug}` + * Changed errors for endpoint `PUT /data-integrations/{slug}/api-key` + * Changed errors for endpoint `PUT /data-integrations/{slug}/client-credentials` + * Changed errors for endpoint `POST /data-integrations/{slug}/credentials` + * Changed errors for endpoint `POST /data-integrations/{provider}/token` + * Changed errors for endpoint `GET /user_management/users/{user_id}/connected_accounts/{slug}` + * Changed errors for endpoint `PUT /user_management/users/{user_id}/connected_accounts/{slug}` + * Changed errors for endpoint `DELETE /user_management/users/{user_id}/connected_accounts/{slug}` + * Changed the type of `DataIntegrationInstallation.user_id` diff --git a/.last-synced-sha b/.last-synced-sha index 2f412fd37..1c2b758d6 100644 --- a/.last-synced-sha +++ b/.last-synced-sha @@ -1 +1 @@ -6fba233c50a5b651b2df7b859b75bfe715bc825a +d3fa61120f8b9e73972b9dc5f4b5ea2bc0526636 diff --git a/.oagen-manifest.json b/.oagen-manifest.json index 59c9d114b..587c9f92b 100644 --- a/.oagen-manifest.json +++ b/.oagen-manifest.json @@ -106,6 +106,7 @@ "src/workos/common/__init__.py", "src/workos/common/models/__init__.py", "src/workos/common/models/access_token_agent_registration_credential_issued_data_detail.py", + "src/workos/common/models/account_selection_required_error.py", "src/workos/common/models/action_authentication_denied.py", "src/workos/common/models/action_authentication_denied_data.py", "src/workos/common/models/action_user_registration_denied.py", @@ -280,6 +281,7 @@ "src/workos/common/models/create_connection_oidc_options_id_token_signature_algorithm.py", "src/workos/common/models/create_connection_oidc_options_token_authentication_method.py", "src/workos/common/models/create_data_integration_auth_methods.py", + "src/workos/common/models/create_data_integration_ownership.py", "src/workos/common/models/create_user_invite_options_locale.py", "src/workos/common/models/create_user_password_hash_type.py", "src/workos/common/models/create_user_password_salt_position.py", @@ -292,11 +294,16 @@ "src/workos/common/models/data_integration_credentials_input_type.py", "src/workos/common/models/data_integration_credentials_response_error.py", "src/workos/common/models/data_integration_custom_provider_authenticate_via.py", + "src/workos/common/models/data_integration_ownership.py", "src/workos/common/models/data_integration_state.py", + "src/workos/common/models/data_integrations_get_user_token_request_connection_owner.py", "src/workos/common/models/data_integrations_list_response_data_auth_methods.py", "src/workos/common/models/data_integrations_list_response_data_connected_account_auth_method.py", "src/workos/common/models/data_integrations_list_response_data_connected_account_state.py", "src/workos/common/models/data_integrations_list_response_data_ownership.py", + "src/workos/common/models/data_integrations_upsert_api_key_request_connection_owner.py", + "src/workos/common/models/data_integrations_upsert_client_credentials_request_connection_owner.py", + "src/workos/common/models/data_integrations_vend_credentials_request_connection_owner.py", "src/workos/common/models/directory_group.py", "src/workos/common/models/directory_state.py", "src/workos/common/models/directory_type.py", @@ -690,6 +697,7 @@ "src/workos/pipes/models/data_integrations_upsert_api_key_request.py", "src/workos/pipes/models/data_integrations_upsert_client_credentials_request.py", "src/workos/pipes/models/data_integrations_vend_credentials_request.py", + "src/workos/pipes/models/pipes_ownership.py", "src/workos/pipes/models/update_custom_provider_definition.py", "src/workos/pipes/models/update_data_integration.py", "src/workos/pipes_provider/__init__.py", @@ -777,10 +785,12 @@ "src/workos/user_management/models/__init__.py", "src/workos/user_management/models/authenticate_response.py", "src/workos/user_management/models/authenticate_response_oauth_token.py", + "src/workos/user_management/models/authkit_oauth_resource.py", "src/workos/user_management/models/authorization_code_session_authenticate_request.py", "src/workos/user_management/models/authorized_connect_application_list_data.py", "src/workos/user_management/models/confirm_email_change.py", "src/workos/user_management/models/cors_origin_response.py", + "src/workos/user_management/models/create_authkit_oauth_resource.py", "src/workos/user_management/models/create_cors_origin.py", "src/workos/user_management/models/create_magic_code_and_return.py", "src/workos/user_management/models/create_password_reset.py", @@ -868,6 +878,7 @@ "src/workos/widgets/models/widget_session_token.py", "src/workos/widgets/models/widget_session_token_response.py", "tests/fixtures/access_token_agent_registration_credential_issued_data_detail.json", + "tests/fixtures/account_selection_required_error.json", "tests/fixtures/action_authentication_denied.json", "tests/fixtures/action_authentication_denied_data.json", "tests/fixtures/action_user_registration_denied.json", @@ -1032,6 +1043,7 @@ "tests/fixtures/authentication_sso_timed_out_data.json", "tests/fixtures/authentication_sso_timed_out_data_error.json", "tests/fixtures/authentication_sso_timed_out_data_sso.json", + "tests/fixtures/authkit_oauth_resource.json", "tests/fixtures/authorization_check.json", "tests/fixtures/authorization_code_session_authenticate_request.json", "tests/fixtures/authorization_permission.json", @@ -1070,6 +1082,7 @@ "tests/fixtures/connection_saml_certificate_renewed_data_certificate.json", "tests/fixtures/connection_saml_certificate_renewed_data_connection.json", "tests/fixtures/cors_origin_response.json", + "tests/fixtures/create_authkit_oauth_resource.json", "tests/fixtures/create_authorization_permission.json", "tests/fixtures/create_authorization_resource.json", "tests/fixtures/create_connection.json", @@ -1249,6 +1262,7 @@ "tests/fixtures/list_audit_log_action.json", "tests/fixtures/list_audit_log_schema.json", "tests/fixtures/list_authentication_factor.json", + "tests/fixtures/list_authkit_oauth_resource.json", "tests/fixtures/list_authorization_permission.json", "tests/fixtures/list_authorization_resource.json", "tests/fixtures/list_authorized_connect_application_list_data.json", @@ -2550,6 +2564,30 @@ "POST /agents/blueprints/{agent_blueprint_id}/tokens/validate": { "sdkMethod": "validate_blueprint_token", "service": "agents" + }, + "GET /data-integrations/{slug}/organization": { + "sdkMethod": "list_data_integration_organization", + "service": "pipes" + }, + "PUT /data-integrations/{slug}/organization": { + "sdkMethod": "update_data_integration_organization", + "service": "pipes" + }, + "DELETE /data-integrations/{slug}/organization": { + "sdkMethod": "delete_data_integration_organization", + "service": "pipes" + }, + "GET /user_management/authkit_oauth_resources": { + "sdkMethod": "list_authkit_oauth_resources", + "service": "user_management" + }, + "POST /user_management/authkit_oauth_resources": { + "sdkMethod": "create_authkit_oauth_resource", + "service": "user_management" + }, + "DELETE /user_management/authkit_oauth_resources/{id}": { + "sdkMethod": "delete_authkit_oauth_resource", + "service": "user_management" } } } diff --git a/src/workos/admin_portal/_resource.py b/src/workos/admin_portal/_resource.py index 9721a5729..79c093483 100644 --- a/src/workos/admin_portal/_resource.py +++ b/src/workos/admin_portal/_resource.py @@ -34,8 +34,8 @@ def generate_link( Generate a Portal Link scoped to an Organization. Args: - return_url: The URL to go to when an admin clicks on your logo in the Admin Portal. If not specified, the return URL configured on the [Redirects](https://dashboard.workos.com/redirects) page will be used. - success_url: The URL to redirect the admin to when they finish setup. If not specified, the success URL configured on the [Redirects](https://dashboard.workos.com/redirects) page will be used. + return_url: The URL to go to when an admin clicks on your logo in the Admin Portal. If not specified, the return URL configured on the [Admin Portal](https://dashboard.workos.com/admin-portal) page will be used. + success_url: The URL to redirect the admin to when they finish setup. If not specified, the success URL configured on the [Admin Portal](https://dashboard.workos.com/admin-portal) page will be used. organization: An [Organization](https://workos.com/docs/reference/organization) identifier. intent: The intent of the Admin Portal. - `sso` - Launch Admin Portal for creating SSO connections @@ -101,8 +101,8 @@ async def generate_link( Generate a Portal Link scoped to an Organization. Args: - return_url: The URL to go to when an admin clicks on your logo in the Admin Portal. If not specified, the return URL configured on the [Redirects](https://dashboard.workos.com/redirects) page will be used. - success_url: The URL to redirect the admin to when they finish setup. If not specified, the success URL configured on the [Redirects](https://dashboard.workos.com/redirects) page will be used. + return_url: The URL to go to when an admin clicks on your logo in the Admin Portal. If not specified, the return URL configured on the [Admin Portal](https://dashboard.workos.com/admin-portal) page will be used. + success_url: The URL to redirect the admin to when they finish setup. If not specified, the success URL configured on the [Admin Portal](https://dashboard.workos.com/admin-portal) page will be used. organization: An [Organization](https://workos.com/docs/reference/organization) identifier. intent: The intent of the Admin Portal. - `sso` - Launch Admin Portal for creating SSO connections diff --git a/src/workos/admin_portal/models/generate_link.py b/src/workos/admin_portal/models/generate_link.py index c3bd20f57..e02c4881b 100644 --- a/src/workos/admin_portal/models/generate_link.py +++ b/src/workos/admin_portal/models/generate_link.py @@ -17,9 +17,9 @@ class GenerateLink: organization: str """An [Organization](https://workos.com/docs/reference/organization) identifier.""" return_url: str | None = None - """The URL to go to when an admin clicks on your logo in the Admin Portal. If not specified, the return URL configured on the [Redirects](https://dashboard.workos.com/redirects) page will be used.""" + """The URL to go to when an admin clicks on your logo in the Admin Portal. If not specified, the return URL configured on the [Admin Portal](https://dashboard.workos.com/admin-portal) page will be used.""" success_url: str | None = None - """The URL to redirect the admin to when they finish setup. If not specified, the success URL configured on the [Redirects](https://dashboard.workos.com/redirects) page will be used.""" + """The URL to redirect the admin to when they finish setup. If not specified, the success URL configured on the [Admin Portal](https://dashboard.workos.com/admin-portal) page will be used.""" intent: GenerateLinkIntent | None = None """ The intent of the Admin Portal. diff --git a/src/workos/agents/_resource.py b/src/workos/agents/_resource.py index b8205e783..34ade28aa 100644 --- a/src/workos/agents/_resource.py +++ b/src/workos/agents/_resource.py @@ -596,6 +596,7 @@ def list_sessions( before: str | None = None, after: str | None = None, order: PaginationOrder | str | None = "desc", + organization_id: str | None = None, agent_blueprint_id: str | None = None, agent_instance_id: str | None = None, request_options: RequestOptions | None = None, @@ -609,6 +610,7 @@ def list_sessions( before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + organization_id: Only return sessions of instances acting within this organization. agent_blueprint_id: Only return sessions of instances minted from this blueprint. agent_instance_id: Only return sessions belonging to this agent instance. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -629,6 +631,7 @@ def list_sessions( "before": before, "after": after, "order": enum_value(order) if order is not None else None, + "organization_id": organization_id, "agent_blueprint_id": agent_blueprint_id, "agent_instance_id": agent_instance_id, }.items() @@ -1265,6 +1268,7 @@ async def list_sessions( before: str | None = None, after: str | None = None, order: PaginationOrder | str | None = "desc", + organization_id: str | None = None, agent_blueprint_id: str | None = None, agent_instance_id: str | None = None, request_options: RequestOptions | None = None, @@ -1278,6 +1282,7 @@ async def list_sessions( before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + organization_id: Only return sessions of instances acting within this organization. agent_blueprint_id: Only return sessions of instances minted from this blueprint. agent_instance_id: Only return sessions belonging to this agent instance. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -1298,6 +1303,7 @@ async def list_sessions( "before": before, "after": after, "order": enum_value(order) if order is not None else None, + "organization_id": organization_id, "agent_blueprint_id": agent_blueprint_id, "agent_instance_id": agent_instance_id, }.items() diff --git a/src/workos/api_keys/models/validate_api_key.py b/src/workos/api_keys/models/validate_api_key.py index 36737894a..276ccaee8 100644 --- a/src/workos/api_keys/models/validate_api_key.py +++ b/src/workos/api_keys/models/validate_api_key.py @@ -1,9 +1,32 @@ # This file is auto-generated by oagen. Do not edit. -from typing import TypeAlias +from __future__ import annotations -from workos.sso.models.create_saml_idp_signing_certificate import ( - CreateSAMLIdpSigningCertificate, -) +from dataclasses import dataclass +from typing import Any -ValidateApiKey: TypeAlias = CreateSAMLIdpSigningCertificate +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class ValidateApiKey: + """Validate Api Key model.""" + + value: str + """The value for an API key.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> ValidateApiKey: + """Deserialize from a dictionary.""" + try: + return cls( + value=data["value"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("ValidateApiKey", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["value"] = self.value + return result diff --git a/src/workos/common/__init__.py b/src/workos/common/__init__.py index df8808992..931b156b0 100644 --- a/src/workos/common/__init__.py +++ b/src/workos/common/__init__.py @@ -4,6 +4,7 @@ from .models import ( AccessTokenAgentRegistrationCredentialIssuedDataDetail as AccessTokenAgentRegistrationCredentialIssuedDataDetail, ) +from .models import AccountSelectionRequiredError as AccountSelectionRequiredError from .models import ActionAuthenticationDenied as ActionAuthenticationDenied from .models import ActionAuthenticationDeniedData as ActionAuthenticationDeniedData from .models import ActionUserRegistrationDenied as ActionUserRegistrationDenied @@ -262,6 +263,7 @@ CreateConnectionOIDCOptionsTokenAuthenticationMethod as CreateConnectionOIDCOptionsTokenAuthenticationMethod, ) from .models import CreateDataIntegrationAuthMethods as CreateDataIntegrationAuthMethods +from .models import CreateDataIntegrationOwnership as CreateDataIntegrationOwnership from .models import CreateUserInviteOptionsLocale as CreateUserInviteOptionsLocale from .models import CreateUserPasswordHashType as CreateUserPasswordHashType from .models import CreateUserPasswordSaltPosition as CreateUserPasswordSaltPosition @@ -286,6 +288,10 @@ from .models import ( DataIntegrationCustomProviderAuthenticateVia as DataIntegrationCustomProviderAuthenticateVia, ) +from .models import DataIntegrationOwnership as DataIntegrationOwnership +from .models import ( + DataIntegrationsGetUserTokenRequestConnectionOwner as DataIntegrationsGetUserTokenRequestConnectionOwner, +) from .models import ( DataIntegrationsListResponseDataAuthMethods as DataIntegrationsListResponseDataAuthMethods, ) @@ -299,6 +305,15 @@ DataIntegrationsListResponseDataOwnership as DataIntegrationsListResponseDataOwnership, ) from .models import DataIntegrationState as DataIntegrationState +from .models import ( + DataIntegrationsUpsertApiKeyRequestConnectionOwner as DataIntegrationsUpsertApiKeyRequestConnectionOwner, +) +from .models import ( + DataIntegrationsUpsertClientCredentialsRequestConnectionOwner as DataIntegrationsUpsertClientCredentialsRequestConnectionOwner, +) +from .models import ( + DataIntegrationsVendCredentialsRequestConnectionOwner as DataIntegrationsVendCredentialsRequestConnectionOwner, +) from .models import DirectoryGroup as DirectoryGroup from .models import DirectoryState as DirectoryState from .models import DirectoryType as DirectoryType diff --git a/src/workos/common/models/__init__.py b/src/workos/common/models/__init__.py index 8e5c38d8e..5890f9ad8 100644 --- a/src/workos/common/models/__init__.py +++ b/src/workos/common/models/__init__.py @@ -3,6 +3,9 @@ from .access_token_agent_registration_credential_issued_data_detail import ( AccessTokenAgentRegistrationCredentialIssuedDataDetail as AccessTokenAgentRegistrationCredentialIssuedDataDetail, ) +from .account_selection_required_error import ( + AccountSelectionRequiredError as AccountSelectionRequiredError, +) from .action_authentication_denied import ( ActionAuthenticationDenied as ActionAuthenticationDenied, ) @@ -429,6 +432,9 @@ from .create_data_integration_auth_methods import ( CreateDataIntegrationAuthMethods as CreateDataIntegrationAuthMethods, ) +from .create_data_integration_ownership import ( + CreateDataIntegrationOwnership as CreateDataIntegrationOwnership, +) from .create_user_invite_options_locale import ( CreateUserInviteOptionsLocale as CreateUserInviteOptionsLocale, ) @@ -465,7 +471,13 @@ from .data_integration_custom_provider_authenticate_via import ( DataIntegrationCustomProviderAuthenticateVia as DataIntegrationCustomProviderAuthenticateVia, ) +from .data_integration_ownership import ( + DataIntegrationOwnership as DataIntegrationOwnership, +) from .data_integration_state import DataIntegrationState as DataIntegrationState +from .data_integrations_get_user_token_request_connection_owner import ( + DataIntegrationsGetUserTokenRequestConnectionOwner as DataIntegrationsGetUserTokenRequestConnectionOwner, +) from .data_integrations_list_response_data_auth_methods import ( DataIntegrationsListResponseDataAuthMethods as DataIntegrationsListResponseDataAuthMethods, ) @@ -478,6 +490,15 @@ from .data_integrations_list_response_data_ownership import ( DataIntegrationsListResponseDataOwnership as DataIntegrationsListResponseDataOwnership, ) +from .data_integrations_upsert_api_key_request_connection_owner import ( + DataIntegrationsUpsertApiKeyRequestConnectionOwner as DataIntegrationsUpsertApiKeyRequestConnectionOwner, +) +from .data_integrations_upsert_client_credentials_request_connection_owner import ( + DataIntegrationsUpsertClientCredentialsRequestConnectionOwner as DataIntegrationsUpsertClientCredentialsRequestConnectionOwner, +) +from .data_integrations_vend_credentials_request_connection_owner import ( + DataIntegrationsVendCredentialsRequestConnectionOwner as DataIntegrationsVendCredentialsRequestConnectionOwner, +) from .directory_group import DirectoryGroup as DirectoryGroup from .directory_state import DirectoryState as DirectoryState from .directory_type import DirectoryType as DirectoryType diff --git a/src/workos/common/models/account_selection_required_error.py b/src/workos/common/models/account_selection_required_error.py new file mode 100644 index 000000000..403ca1655 --- /dev/null +++ b/src/workos/common/models/account_selection_required_error.py @@ -0,0 +1,36 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Literal + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class AccountSelectionRequiredError: + """Account Selection Required Error model.""" + + code: Literal["account_selection_required"] + """Indicates that several connected accounts match and the request must name one.""" + message: str + """A human-readable explanation of the ambiguity.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AccountSelectionRequiredError: + """Deserialize from a dictionary.""" + try: + return cls( + code=data.get("code", "account_selection_required"), + message=data["message"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AccountSelectionRequiredError", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["code"] = self.code + result["message"] = self.message + return result diff --git a/src/workos/common/models/create_data_integration_ownership.py b/src/workos/common/models/create_data_integration_ownership.py new file mode 100644 index 000000000..f2eef58b5 --- /dev/null +++ b/src/workos/common/models/create_data_integration_ownership.py @@ -0,0 +1,27 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of create data integration ownership values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class CreateDataIntegrationOwnership(str, Enum): + """Known values for CreateDataIntegrationOwnership.""" + + USER = "user" + ORGANIZATION = "organization" + + @classmethod + def _missing_(cls, value: object) -> CreateDataIntegrationOwnership | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +CreateDataIntegrationOwnershipLiteral: TypeAlias = Literal["user", "organization"] diff --git a/src/workos/common/models/data_integration_ownership.py b/src/workos/common/models/data_integration_ownership.py new file mode 100644 index 000000000..98aac7784 --- /dev/null +++ b/src/workos/common/models/data_integration_ownership.py @@ -0,0 +1,8 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .create_data_integration_ownership import CreateDataIntegrationOwnership + +DataIntegrationOwnership: TypeAlias = CreateDataIntegrationOwnership +__all__ = ["DataIntegrationOwnership"] diff --git a/src/workos/common/models/data_integrations_get_user_token_request_connection_owner.py b/src/workos/common/models/data_integrations_get_user_token_request_connection_owner.py new file mode 100644 index 000000000..49047c493 --- /dev/null +++ b/src/workos/common/models/data_integrations_get_user_token_request_connection_owner.py @@ -0,0 +1,10 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .create_data_integration_ownership import CreateDataIntegrationOwnership + +DataIntegrationsGetUserTokenRequestConnectionOwner: TypeAlias = ( + CreateDataIntegrationOwnership +) +__all__ = ["DataIntegrationsGetUserTokenRequestConnectionOwner"] diff --git a/src/workos/common/models/data_integrations_upsert_api_key_request_connection_owner.py b/src/workos/common/models/data_integrations_upsert_api_key_request_connection_owner.py new file mode 100644 index 000000000..3a5630ac9 --- /dev/null +++ b/src/workos/common/models/data_integrations_upsert_api_key_request_connection_owner.py @@ -0,0 +1,10 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .create_data_integration_ownership import CreateDataIntegrationOwnership + +DataIntegrationsUpsertApiKeyRequestConnectionOwner: TypeAlias = ( + CreateDataIntegrationOwnership +) +__all__ = ["DataIntegrationsUpsertApiKeyRequestConnectionOwner"] diff --git a/src/workos/common/models/data_integrations_upsert_client_credentials_request_connection_owner.py b/src/workos/common/models/data_integrations_upsert_client_credentials_request_connection_owner.py new file mode 100644 index 000000000..f67f82758 --- /dev/null +++ b/src/workos/common/models/data_integrations_upsert_client_credentials_request_connection_owner.py @@ -0,0 +1,10 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .create_data_integration_ownership import CreateDataIntegrationOwnership + +DataIntegrationsUpsertClientCredentialsRequestConnectionOwner: TypeAlias = ( + CreateDataIntegrationOwnership +) +__all__ = ["DataIntegrationsUpsertClientCredentialsRequestConnectionOwner"] diff --git a/src/workos/common/models/data_integrations_vend_credentials_request_connection_owner.py b/src/workos/common/models/data_integrations_vend_credentials_request_connection_owner.py new file mode 100644 index 000000000..6569cbfbb --- /dev/null +++ b/src/workos/common/models/data_integrations_vend_credentials_request_connection_owner.py @@ -0,0 +1,10 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .create_data_integration_ownership import CreateDataIntegrationOwnership + +DataIntegrationsVendCredentialsRequestConnectionOwner: TypeAlias = ( + CreateDataIntegrationOwnership +) +__all__ = ["DataIntegrationsVendCredentialsRequestConnectionOwner"] diff --git a/src/workos/common/models/resource_export_completed_data_resource_type.py b/src/workos/common/models/resource_export_completed_data_resource_type.py index 47f02db00..4d08f5cc4 100644 --- a/src/workos/common/models/resource_export_completed_data_resource_type.py +++ b/src/workos/common/models/resource_export_completed_data_resource_type.py @@ -16,6 +16,7 @@ class ResourceExportCompletedDataResourceType(str, Enum): EVENTS = "events" SESSIONS = "sessions" AUDIT_LOG_EVENTS = "auditLogEvents" + CONNECTIONS = "connections" @classmethod def _missing_(cls, value: object) -> ResourceExportCompletedDataResourceType | None: @@ -28,5 +29,5 @@ def _missing_(cls, value: object) -> ResourceExportCompletedDataResourceType | N ResourceExportCompletedDataResourceTypeLiteral: TypeAlias = Literal[ - "users", "organizations", "events", "sessions", "auditLogEvents" + "users", "organizations", "events", "sessions", "auditLogEvents", "connections" ] diff --git a/src/workos/organizations/models/audit_logs_retention.py b/src/workos/organizations/models/audit_logs_retention.py index 6a557706c..da4e68d1f 100644 --- a/src/workos/organizations/models/audit_logs_retention.py +++ b/src/workos/organizations/models/audit_logs_retention.py @@ -13,7 +13,7 @@ class AuditLogsRetention: """Audit Logs Retention model.""" retention_period_in_days: int | None - """The number of days Audit Log events will be retained before being permanently deleted. Valid values are 30 and 365.""" + """The number of days Audit Log events will be retained before being permanently deleted. Valid values are 30 through 330 in 30-day increments and 365 through 3650 in 365-day increments.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> AuditLogsRetention: diff --git a/src/workos/pipes/_resource.py b/src/workos/pipes/_resource.py index 950eb8c87..a9287d729 100644 --- a/src/workos/pipes/_resource.py +++ b/src/workos/pipes/_resource.py @@ -14,6 +14,21 @@ from workos.common.models.create_data_integration_auth_methods import ( CreateDataIntegrationAuthMethods, ) +from workos.common.models.create_data_integration_ownership import ( + CreateDataIntegrationOwnership, +) +from workos.common.models.data_integrations_get_user_token_request_connection_owner import ( + DataIntegrationsGetUserTokenRequestConnectionOwner, +) +from workos.common.models.data_integrations_upsert_api_key_request_connection_owner import ( + DataIntegrationsUpsertApiKeyRequestConnectionOwner, +) +from workos.common.models.data_integrations_upsert_client_credentials_request_connection_owner import ( + DataIntegrationsUpsertClientCredentialsRequestConnectionOwner, +) +from workos.common.models.data_integrations_vend_credentials_request_connection_owner import ( + DataIntegrationsVendCredentialsRequestConnectionOwner, +) from workos.common.models.pagination_order import PaginationOrder from .._pagination import AsyncPage, SyncPage @@ -27,6 +42,7 @@ DataIntegrationCredentialsInput, DataIntegrationCredentialsResponse, DataIntegrationsListResponse, + PipesOwnership, UpdateCustomProviderDefinition, ) @@ -44,17 +60,19 @@ def list_data_integrations( before: str | None = None, after: str | None = None, order: PaginationOrder | str | None = "desc", + ownership: PipesOwnership | str | None = None, request_options: RequestOptions | None = None, ) -> SyncPage[DataIntegration]: """List data integrations - Lists the environment's data integrations configured with `custom` or `organization` credentials, including custom providers and API key integrations. + Lists the environment's data integrations configured with `custom` or `organization` credentials, including custom providers and API key integrations. Both user-owned and organization-owned roots are returned, each as its own row with an `ownership`; filter with `ownership` to return only one kind. Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + ownership: Only return Data Integrations with this ownership: `user` for the integrations users connect their own accounts to, or `organization` for the roots organizations connect to. Omit to return both. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -62,6 +80,8 @@ def list_data_integrations( Raises: AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ @@ -72,6 +92,7 @@ def list_data_integrations( "before": before, "after": after, "order": enum_value(order) if order is not None else None, + "ownership": enum_value(ownership) if ownership is not None else None, }.items() if v is not None } @@ -87,6 +108,7 @@ def create_data_integration( self, *, provider: str, + ownership: CreateDataIntegrationOwnership | str | None = None, description: str | None | NotGiven = NOT_GIVEN, enabled: bool | None = None, scopes: list[str] | None | NotGiven = NOT_GIVEN, @@ -99,10 +121,11 @@ def create_data_integration( ) -> DataIntegration: """Create a data integration - Creates a data integration for a provider. Set `credentials.type` to `custom` to use your own OAuth app credentials or `organization` to have each organization supply its own. Set `auth_methods` to `["api_key"]` to create an API key integration; you may optionally supply an `api_key` block to install a first tenant in the same call. Set `auth_methods` to `["client_credentials"]` to create a client-credentials integration; client credentials are installed per-tenant afterwards. For a built-in provider, pass its slug as `provider`. For a custom provider, pass a new slug plus a `custom_provider` definition. + Creates a data integration for a provider. Set `credentials.type` to `custom` to use your own OAuth app credentials or `organization` to have each organization supply its own. Set `auth_methods` to `["api_key"]` to create an API key integration; you may optionally supply an `api_key` block to install a first tenant in the same call. Set `auth_methods` to `["client_credentials"]` to create a client-credentials integration; client credentials are installed per-tenant afterwards. Set `ownership` to `organization` to create the integration organizations connect to instead of the default user-owned one; a provider may have one of each. For a built-in provider, pass its slug as `provider`. For a custom provider, pass a new slug plus a `custom_provider` definition, or the slug of an existing custom provider (without `custom_provider`) to add the other ownership. Args: provider: The provider to create a Data Integration for. For a built-in provider use its slug (e.g. `github`, `slack`). For a custom provider, this is the new provider slug and `custom_provider` must be supplied. A custom provider slug cannot shadow an existing global provider slug. + ownership: Who owns the Data Integration. `user` (the default) creates the integration users connect their own accounts to; `organization` creates the root organizations connect to. Ownership is fixed at creation, and one integration of each ownership may exist per provider. Independent of `credentials.type`. description: An optional description of the Data Integration. enabled: Whether the Data Integration is enabled. Defaults to `false`. scopes: The OAuth scopes to request for the Data Integration. Defaults to the provider's configured scopes when omitted. @@ -130,6 +153,7 @@ def create_data_integration( k: v for k, v in { "provider": provider, + "ownership": enum_value(ownership) if ownership is not None else None, "enabled": enabled, "auth_methods": auth_methods, "config": config, @@ -163,7 +187,7 @@ def get_data_integration( ) -> DataIntegration: """Get a data integration - Retrieves a data integration by its slug. + Retrieves the user-owned data integration by its slug. Args: slug: The slug identifier of the data integration. @@ -199,7 +223,7 @@ def update_data_integration( ) -> DataIntegration: """Update a data integration - Updates the description, enabled state, or custom credentials of a data integration. For custom providers, `custom_provider` updates the OAuth definition. + Updates the description, enabled state, or custom credentials of the user-owned data integration. For custom providers, `custom_provider` updates the OAuth definition. Args: slug: The slug identifier of the data integration. @@ -258,7 +282,7 @@ def delete_data_integration( ) -> None: """Delete a data integration - Deletes a data integration and all of its connected installations. For a custom provider, also deletes the custom provider definition. + Deletes the user-owned data integration and all of its connected installations. For a custom provider, the provider definition is deleted once no organization-owned root references it either. Args: slug: The slug identifier of the data integration. @@ -283,16 +307,22 @@ def update_data_integration_api_key( user_id: str, secret: str, organization_id: str | None = None, + connected_account_id: str | None = None, + connection_owner: DataIntegrationsUpsertApiKeyRequestConnectionOwner + | str + | None = None, request_options: RequestOptions | None = None, ) -> ConnectedAccount: """Upsert an API key for a connected account - Creates or updates an API-key-based installation for the specified integration and user. If an installation already exists, the stored API key is rotated to the new value. + Creates or updates an API-key-based installation for the specified integration, owned by the user or, when `connection_owner` is `organization`, shared by the organization. If an installation already exists, the stored API key is rotated to the new value. Args: slug: The identifier of the integration. user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. Required when `connection_owner` is `organization`. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to rotate a specific existing connection. + connection_owner: Whose connection to create or rotate. `user` (the default) addresses the connection owned by `user_id`. `organization` addresses the connection shared by every member of `organization_id`; `user_id` then identifies the member performing the request and must be an active member of the organization. secret: The API key secret to store for this integration. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -314,6 +344,10 @@ def update_data_integration_api_key( for k, v in { "user_id": user_id, "organization_id": organization_id, + "connected_account_id": connected_account_id, + "connection_owner": enum_value(connection_owner) + if connection_owner is not None + else None, "secret": secret, }.items() if v is not None @@ -385,17 +419,23 @@ def update_data_integration_client_credentials( client_id: str, client_secret: str, organization_id: str | None = None, + connected_account_id: str | None = None, + connection_owner: DataIntegrationsUpsertClientCredentialsRequestConnectionOwner + | str + | None = None, config: dict[str, str] | None = None, request_options: RequestOptions | None = None, ) -> ConnectedAccount: """Upsert client credentials for a connected account - Creates or updates a client-credentials-based installation for the specified integration and user. If an installation already exists, the stored client credentials are rotated to the new values. + Creates or updates a client-credentials-based installation for the specified integration, owned by the user or, when `connection_owner` is `organization`, shared by the organization. If an installation already exists, the stored client credentials are rotated to the new values. Args: slug: The identifier of the integration. user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. Required when `connection_owner` is `organization`. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to rotate a specific existing connection. + connection_owner: Whose connection to create or rotate. `user` (the default) addresses the connection owned by `user_id`. `organization` addresses the connection shared by every member of `organization_id`; `user_id` then identifies the member performing the request and must be an active member of the organization. client_id: The OAuth client ID to store for this integration. client_secret: The OAuth client secret to store for this integration. config: Provider-specific configuration values collected for this installation, keyed by the provider's config field descriptors. @@ -419,6 +459,10 @@ def update_data_integration_client_credentials( for k, v in { "user_id": user_id, "organization_id": organization_id, + "connected_account_id": connected_account_id, + "connection_owner": enum_value(connection_owner) + if connection_owner is not None + else None, "client_id": client_id, "client_secret": client_secret, "config": config, @@ -440,6 +484,10 @@ def create_data_integration_credential( user_id: str, organization_id: str | None = None, connected_account_id: str | None = None, + connection_owner: DataIntegrationsVendCredentialsRequestConnectionOwner + | str + | None = None, + supports_multiple_connections: bool | None = None, request_options: RequestOptions | None = None, ) -> DataIntegrationCredentialsResponse: """Vend credentials for a connected account @@ -448,9 +496,11 @@ def create_data_integration_credential( Args: slug: The identifier of the integration. - user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. + user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. When `connection_owner` is `organization`, this is the user the credentials are vended on behalf of; they must be an active member of the organization. + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. Required when `connection_owner` is `organization`. connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider. + connection_owner: Which connection to vend from. `user` (the default) vends the user's own connection and requires `user_id`. `organization` vends the organization's shared connection and requires `organization_id`. + supports_multiple_connections: Set to `true` to use the plural connection contract. If no `connected_account_id` is supplied and several connections match, the request returns `account_selection_required`. When omitted or `false`, only the compatibility connection is considered. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -459,6 +509,7 @@ def create_data_integration_credential( Raises: BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). + AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). @@ -471,6 +522,10 @@ def create_data_integration_credential( "user_id": user_id, "organization_id": organization_id, "connected_account_id": connected_account_id, + "connection_owner": enum_value(connection_owner) + if connection_owner is not None + else None, + "supports_multiple_connections": supports_multiple_connections, }.items() if v is not None } @@ -482,6 +537,127 @@ def create_data_integration_credential( request_options=request_options, ) + def list_data_integration_organization( + self, + slug: str, + *, + request_options: RequestOptions | None = None, + ) -> DataIntegration: + """Get an organization-owned data integration + + Retrieves the organization-owned data integration for a provider by its slug. The `/organization` suffix selects the environment-level organization-owned root for the provider; it does not name a particular organization. + + Args: + slug: The slug identifier of the data integration. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + DataIntegration + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="get", + path=("data-integrations", str(slug), "organization"), + model=DataIntegration, + request_options=request_options, + ) + + def update_data_integration_organization( + self, + slug: str, + *, + description: str | None | NotGiven = NOT_GIVEN, + enabled: bool | None = None, + scopes: list[str] | None | NotGiven = NOT_GIVEN, + credentials: DataIntegrationCredentialsInput | None = None, + api_key: ApiKeyInstallation | None = None, + custom_provider: UpdateCustomProviderDefinition | None = None, + request_options: RequestOptions | None = None, + ) -> DataIntegration: + """Update an organization-owned data integration + + Updates the description, enabled state, or custom credentials of the organization-owned data integration for a provider. For custom providers, `custom_provider` updates the OAuth definition, which is shared with the user-owned root. The `/organization` suffix selects the environment-level organization-owned root for the provider; it does not name a particular organization. + + Args: + slug: The slug identifier of the data integration. + description: An optional description of the Data Integration. + enabled: Whether the Data Integration is enabled. + scopes: The OAuth scopes to request for the Data Integration. Pass `null` to reset to the provider's configured scopes. + credentials: New OAuth credentials for the Data Integration. When provided, rotates the stored client secret. Mutually exclusive with `api_key`. + api_key: An API key to install or rotate for a tenant on an `api_key` integration. Upserts the tenant installation identified by `user_id` (and optional `organization_id`). + custom_provider: Updates to a custom provider's OAuth definition. Only valid for custom-provider integrations. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + DataIntegration + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "enabled": enabled, + "credentials": credentials.to_dict() + if credentials is not None + else None, + "api_key": api_key.to_dict() if api_key is not None else None, + "custom_provider": custom_provider.to_dict() + if custom_provider is not None + else None, + }.items() + if v is not None + } + if not isinstance(description, NotGiven): + body["description"] = description + if not isinstance(scopes, NotGiven): + body["scopes"] = scopes + return self._client.request( + method="put", + path=("data-integrations", str(slug), "organization"), + body=body, + model=DataIntegration, + request_options=request_options, + ) + + def delete_data_integration_organization( + self, + slug: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an organization-owned data integration + + Deletes the organization-owned data integration for a provider and all of its connected installations. For a custom provider, the provider definition is deleted once no user-owned root references it either. The `/organization` suffix selects the environment-level organization-owned root for the provider; it does not name a particular organization. + + Args: + slug: The slug identifier of the data integration. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + self._client.request( + method="delete", + path=("data-integrations", str(slug), "organization"), + request_options=request_options, + ) + def get_access_token( self, provider: str, @@ -489,6 +665,10 @@ def get_access_token( user_id: str, organization_id: str | None | NotGiven = NOT_GIVEN, connected_account_id: str | None = None, + connection_owner: DataIntegrationsGetUserTokenRequestConnectionOwner + | str + | None = None, + supports_multiple_connections: bool | None = None, request_options: RequestOptions | None = None, ) -> DataIntegrationAccessTokenResponse: """Get an access token for a connected account @@ -497,9 +677,11 @@ def get_access_token( Args: provider: The identifier of the integration. - user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. + user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. When `connection_owner` is `organization`, this is the user the credentials are vended on behalf of; they must be an active member of the organization. + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. Required when `connection_owner` is `organization`. connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider. + connection_owner: Which connection to vend from. `user` (the default) vends the user's own connection and requires `user_id`. `organization` vends the organization's shared connection and requires `organization_id`. + supports_multiple_connections: Set to `true` to use the plural connection contract. If no `connected_account_id` is supplied and several connections match, the request returns `account_selection_required`. When omitted or `false`, only the compatibility connection is considered. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -508,6 +690,7 @@ def get_access_token( Raises: BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). + AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). @@ -519,6 +702,10 @@ def get_access_token( for k, v in { "user_id": user_id, "connected_account_id": connected_account_id, + "connection_owner": enum_value(connection_owner) + if connection_owner is not None + else None, + "supports_multiple_connections": supports_multiple_connections, }.items() if v is not None } @@ -538,6 +725,7 @@ def get_user_connected_account( slug: str, *, organization_id: str | None = None, + supports_multiple_connections: bool | None = None, connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> ConnectedAccount: @@ -549,6 +737,7 @@ def get_user_connected_account( user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -567,6 +756,7 @@ def get_user_connected_account( k: v for k, v in { "organization_id": organization_id, + "supports_multiple_connections": supports_multiple_connections, "connected_account_id": connected_account_id, }.items() if v is not None @@ -668,6 +858,7 @@ def update_user_connected_account( scopes: list[str] | None = None, state: ConnectedAccountInputState | str | None = None, organization_id: str | None = None, + supports_multiple_connections: bool | None = None, connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> ConnectedAccount: @@ -684,6 +875,7 @@ def update_user_connected_account( scopes: The OAuth scopes granted for this connection. state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to update. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -695,6 +887,7 @@ def update_user_connected_account( AuthenticationError: If the API key is invalid (401). NotFoundError: If the resource is not found (404). ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ @@ -713,6 +906,7 @@ def update_user_connected_account( k: v for k, v in { "organization_id": organization_id, + "supports_multiple_connections": supports_multiple_connections, "connected_account_id": connected_account_id, }.items() if v is not None @@ -738,17 +932,19 @@ def delete_user_connected_account( slug: str, *, organization_id: str | None = None, + supports_multiple_connections: bool | None = None, connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> None: """Delete a connected account - Disconnects WorkOS's account for the user, including removing any stored access and refresh tokens. The user will need to reauthorize if they want to reconnect. This does not revoke access on the provider side. + Disconnects WorkOS's account for the user, including removing any stored access and refresh tokens. The user will need to reauthorize if they want to reconnect. Access is not revoked on the provider side, except for the WorkOS OAuth provider, whose underlying AuthKit grant is revoked. Args: user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to delete. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -764,6 +960,7 @@ def delete_user_connected_account( k: v for k, v in { "organization_id": organization_id, + "supports_multiple_connections": supports_multiple_connections, "connected_account_id": connected_account_id, }.items() if v is not None @@ -786,6 +983,7 @@ def list_user_data_providers( user_id: str, *, organization_id: str | None = None, + supports_multiple_connections: bool | None = None, request_options: RequestOptions | None = None, ) -> DataIntegrationsListResponse: """List providers for a user @@ -795,6 +993,7 @@ def list_user_data_providers( Args: user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier to list providers and connected accounts for. organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to filter connections for a specific organization. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -810,6 +1009,7 @@ def list_user_data_providers( k: v for k, v in { "organization_id": organization_id, + "supports_multiple_connections": supports_multiple_connections, }.items() if v is not None } @@ -835,17 +1035,19 @@ async def list_data_integrations( before: str | None = None, after: str | None = None, order: PaginationOrder | str | None = "desc", + ownership: PipesOwnership | str | None = None, request_options: RequestOptions | None = None, ) -> AsyncPage[DataIntegration]: """List data integrations - Lists the environment's data integrations configured with `custom` or `organization` credentials, including custom providers and API key integrations. + Lists the environment's data integrations configured with `custom` or `organization` credentials, including custom providers and API key integrations. Both user-owned and organization-owned roots are returned, each as its own row with an `ownership`; filter with `ownership` to return only one kind. Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + ownership: Only return Data Integrations with this ownership: `user` for the integrations users connect their own accounts to, or `organization` for the roots organizations connect to. Omit to return both. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -853,6 +1055,8 @@ async def list_data_integrations( Raises: AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ @@ -863,6 +1067,7 @@ async def list_data_integrations( "before": before, "after": after, "order": enum_value(order) if order is not None else None, + "ownership": enum_value(ownership) if ownership is not None else None, }.items() if v is not None } @@ -878,6 +1083,7 @@ async def create_data_integration( self, *, provider: str, + ownership: CreateDataIntegrationOwnership | str | None = None, description: str | None | NotGiven = NOT_GIVEN, enabled: bool | None = None, scopes: list[str] | None | NotGiven = NOT_GIVEN, @@ -890,10 +1096,11 @@ async def create_data_integration( ) -> DataIntegration: """Create a data integration - Creates a data integration for a provider. Set `credentials.type` to `custom` to use your own OAuth app credentials or `organization` to have each organization supply its own. Set `auth_methods` to `["api_key"]` to create an API key integration; you may optionally supply an `api_key` block to install a first tenant in the same call. Set `auth_methods` to `["client_credentials"]` to create a client-credentials integration; client credentials are installed per-tenant afterwards. For a built-in provider, pass its slug as `provider`. For a custom provider, pass a new slug plus a `custom_provider` definition. + Creates a data integration for a provider. Set `credentials.type` to `custom` to use your own OAuth app credentials or `organization` to have each organization supply its own. Set `auth_methods` to `["api_key"]` to create an API key integration; you may optionally supply an `api_key` block to install a first tenant in the same call. Set `auth_methods` to `["client_credentials"]` to create a client-credentials integration; client credentials are installed per-tenant afterwards. Set `ownership` to `organization` to create the integration organizations connect to instead of the default user-owned one; a provider may have one of each. For a built-in provider, pass its slug as `provider`. For a custom provider, pass a new slug plus a `custom_provider` definition, or the slug of an existing custom provider (without `custom_provider`) to add the other ownership. Args: provider: The provider to create a Data Integration for. For a built-in provider use its slug (e.g. `github`, `slack`). For a custom provider, this is the new provider slug and `custom_provider` must be supplied. A custom provider slug cannot shadow an existing global provider slug. + ownership: Who owns the Data Integration. `user` (the default) creates the integration users connect their own accounts to; `organization` creates the root organizations connect to. Ownership is fixed at creation, and one integration of each ownership may exist per provider. Independent of `credentials.type`. description: An optional description of the Data Integration. enabled: Whether the Data Integration is enabled. Defaults to `false`. scopes: The OAuth scopes to request for the Data Integration. Defaults to the provider's configured scopes when omitted. @@ -921,6 +1128,7 @@ async def create_data_integration( k: v for k, v in { "provider": provider, + "ownership": enum_value(ownership) if ownership is not None else None, "enabled": enabled, "auth_methods": auth_methods, "config": config, @@ -954,7 +1162,7 @@ async def get_data_integration( ) -> DataIntegration: """Get a data integration - Retrieves a data integration by its slug. + Retrieves the user-owned data integration by its slug. Args: slug: The slug identifier of the data integration. @@ -990,7 +1198,7 @@ async def update_data_integration( ) -> DataIntegration: """Update a data integration - Updates the description, enabled state, or custom credentials of a data integration. For custom providers, `custom_provider` updates the OAuth definition. + Updates the description, enabled state, or custom credentials of the user-owned data integration. For custom providers, `custom_provider` updates the OAuth definition. Args: slug: The slug identifier of the data integration. @@ -1049,7 +1257,7 @@ async def delete_data_integration( ) -> None: """Delete a data integration - Deletes a data integration and all of its connected installations. For a custom provider, also deletes the custom provider definition. + Deletes the user-owned data integration and all of its connected installations. For a custom provider, the provider definition is deleted once no organization-owned root references it either. Args: slug: The slug identifier of the data integration. @@ -1074,16 +1282,22 @@ async def update_data_integration_api_key( user_id: str, secret: str, organization_id: str | None = None, + connected_account_id: str | None = None, + connection_owner: DataIntegrationsUpsertApiKeyRequestConnectionOwner + | str + | None = None, request_options: RequestOptions | None = None, ) -> ConnectedAccount: """Upsert an API key for a connected account - Creates or updates an API-key-based installation for the specified integration and user. If an installation already exists, the stored API key is rotated to the new value. + Creates or updates an API-key-based installation for the specified integration, owned by the user or, when `connection_owner` is `organization`, shared by the organization. If an installation already exists, the stored API key is rotated to the new value. Args: slug: The identifier of the integration. user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. Required when `connection_owner` is `organization`. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to rotate a specific existing connection. + connection_owner: Whose connection to create or rotate. `user` (the default) addresses the connection owned by `user_id`. `organization` addresses the connection shared by every member of `organization_id`; `user_id` then identifies the member performing the request and must be an active member of the organization. secret: The API key secret to store for this integration. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -1105,6 +1319,10 @@ async def update_data_integration_api_key( for k, v in { "user_id": user_id, "organization_id": organization_id, + "connected_account_id": connected_account_id, + "connection_owner": enum_value(connection_owner) + if connection_owner is not None + else None, "secret": secret, }.items() if v is not None @@ -1176,17 +1394,23 @@ async def update_data_integration_client_credentials( client_id: str, client_secret: str, organization_id: str | None = None, + connected_account_id: str | None = None, + connection_owner: DataIntegrationsUpsertClientCredentialsRequestConnectionOwner + | str + | None = None, config: dict[str, str] | None = None, request_options: RequestOptions | None = None, ) -> ConnectedAccount: """Upsert client credentials for a connected account - Creates or updates a client-credentials-based installation for the specified integration and user. If an installation already exists, the stored client credentials are rotated to the new values. + Creates or updates a client-credentials-based installation for the specified integration, owned by the user or, when `connection_owner` is `organization`, shared by the organization. If an installation already exists, the stored client credentials are rotated to the new values. Args: slug: The identifier of the integration. user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. Required when `connection_owner` is `organization`. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to rotate a specific existing connection. + connection_owner: Whose connection to create or rotate. `user` (the default) addresses the connection owned by `user_id`. `organization` addresses the connection shared by every member of `organization_id`; `user_id` then identifies the member performing the request and must be an active member of the organization. client_id: The OAuth client ID to store for this integration. client_secret: The OAuth client secret to store for this integration. config: Provider-specific configuration values collected for this installation, keyed by the provider's config field descriptors. @@ -1210,6 +1434,10 @@ async def update_data_integration_client_credentials( for k, v in { "user_id": user_id, "organization_id": organization_id, + "connected_account_id": connected_account_id, + "connection_owner": enum_value(connection_owner) + if connection_owner is not None + else None, "client_id": client_id, "client_secret": client_secret, "config": config, @@ -1231,6 +1459,10 @@ async def create_data_integration_credential( user_id: str, organization_id: str | None = None, connected_account_id: str | None = None, + connection_owner: DataIntegrationsVendCredentialsRequestConnectionOwner + | str + | None = None, + supports_multiple_connections: bool | None = None, request_options: RequestOptions | None = None, ) -> DataIntegrationCredentialsResponse: """Vend credentials for a connected account @@ -1239,9 +1471,11 @@ async def create_data_integration_credential( Args: slug: The identifier of the integration. - user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. + user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. When `connection_owner` is `organization`, this is the user the credentials are vended on behalf of; they must be an active member of the organization. + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. Required when `connection_owner` is `organization`. connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider. + connection_owner: Which connection to vend from. `user` (the default) vends the user's own connection and requires `user_id`. `organization` vends the organization's shared connection and requires `organization_id`. + supports_multiple_connections: Set to `true` to use the plural connection contract. If no `connected_account_id` is supplied and several connections match, the request returns `account_selection_required`. When omitted or `false`, only the compatibility connection is considered. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -1250,6 +1484,7 @@ async def create_data_integration_credential( Raises: BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). + AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). @@ -1262,6 +1497,10 @@ async def create_data_integration_credential( "user_id": user_id, "organization_id": organization_id, "connected_account_id": connected_account_id, + "connection_owner": enum_value(connection_owner) + if connection_owner is not None + else None, + "supports_multiple_connections": supports_multiple_connections, }.items() if v is not None } @@ -1273,6 +1512,127 @@ async def create_data_integration_credential( request_options=request_options, ) + async def list_data_integration_organization( + self, + slug: str, + *, + request_options: RequestOptions | None = None, + ) -> DataIntegration: + """Get an organization-owned data integration + + Retrieves the organization-owned data integration for a provider by its slug. The `/organization` suffix selects the environment-level organization-owned root for the provider; it does not name a particular organization. + + Args: + slug: The slug identifier of the data integration. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + DataIntegration + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="get", + path=("data-integrations", str(slug), "organization"), + model=DataIntegration, + request_options=request_options, + ) + + async def update_data_integration_organization( + self, + slug: str, + *, + description: str | None | NotGiven = NOT_GIVEN, + enabled: bool | None = None, + scopes: list[str] | None | NotGiven = NOT_GIVEN, + credentials: DataIntegrationCredentialsInput | None = None, + api_key: ApiKeyInstallation | None = None, + custom_provider: UpdateCustomProviderDefinition | None = None, + request_options: RequestOptions | None = None, + ) -> DataIntegration: + """Update an organization-owned data integration + + Updates the description, enabled state, or custom credentials of the organization-owned data integration for a provider. For custom providers, `custom_provider` updates the OAuth definition, which is shared with the user-owned root. The `/organization` suffix selects the environment-level organization-owned root for the provider; it does not name a particular organization. + + Args: + slug: The slug identifier of the data integration. + description: An optional description of the Data Integration. + enabled: Whether the Data Integration is enabled. + scopes: The OAuth scopes to request for the Data Integration. Pass `null` to reset to the provider's configured scopes. + credentials: New OAuth credentials for the Data Integration. When provided, rotates the stored client secret. Mutually exclusive with `api_key`. + api_key: An API key to install or rotate for a tenant on an `api_key` integration. Upserts the tenant installation identified by `user_id` (and optional `organization_id`). + custom_provider: Updates to a custom provider's OAuth definition. Only valid for custom-provider integrations. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + DataIntegration + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + AuthorizationError: If the request is forbidden (403). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "enabled": enabled, + "credentials": credentials.to_dict() + if credentials is not None + else None, + "api_key": api_key.to_dict() if api_key is not None else None, + "custom_provider": custom_provider.to_dict() + if custom_provider is not None + else None, + }.items() + if v is not None + } + if not isinstance(description, NotGiven): + body["description"] = description + if not isinstance(scopes, NotGiven): + body["scopes"] = scopes + return await self._client.request( + method="put", + path=("data-integrations", str(slug), "organization"), + body=body, + model=DataIntegration, + request_options=request_options, + ) + + async def delete_data_integration_organization( + self, + slug: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an organization-owned data integration + + Deletes the organization-owned data integration for a provider and all of its connected installations. For a custom provider, the provider definition is deleted once no user-owned root references it either. The `/organization` suffix selects the environment-level organization-owned root for the provider; it does not name a particular organization. + + Args: + slug: The slug identifier of the data integration. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + await self._client.request( + method="delete", + path=("data-integrations", str(slug), "organization"), + request_options=request_options, + ) + async def get_access_token( self, provider: str, @@ -1280,6 +1640,10 @@ async def get_access_token( user_id: str, organization_id: str | None | NotGiven = NOT_GIVEN, connected_account_id: str | None = None, + connection_owner: DataIntegrationsGetUserTokenRequestConnectionOwner + | str + | None = None, + supports_multiple_connections: bool | None = None, request_options: RequestOptions | None = None, ) -> DataIntegrationAccessTokenResponse: """Get an access token for a connected account @@ -1288,9 +1652,11 @@ async def get_access_token( Args: provider: The identifier of the integration. - user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. + user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. When `connection_owner` is `organization`, this is the user the credentials are vended on behalf of; they must be an active member of the organization. + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. Required when `connection_owner` is `organization`. connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider. + connection_owner: Which connection to vend from. `user` (the default) vends the user's own connection and requires `user_id`. `organization` vends the organization's shared connection and requires `organization_id`. + supports_multiple_connections: Set to `true` to use the plural connection contract. If no `connected_account_id` is supplied and several connections match, the request returns `account_selection_required`. When omitted or `false`, only the compatibility connection is considered. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -1299,6 +1665,7 @@ async def get_access_token( Raises: BadRequestError: If the request is malformed (400). AuthenticationError: If the API key is invalid (401). + AuthorizationError: If the request is forbidden (403). NotFoundError: If the resource is not found (404). ConflictError: If a conflict occurs (409). UnprocessableEntityError: If the request data is unprocessable (422). @@ -1310,6 +1677,10 @@ async def get_access_token( for k, v in { "user_id": user_id, "connected_account_id": connected_account_id, + "connection_owner": enum_value(connection_owner) + if connection_owner is not None + else None, + "supports_multiple_connections": supports_multiple_connections, }.items() if v is not None } @@ -1329,6 +1700,7 @@ async def get_user_connected_account( slug: str, *, organization_id: str | None = None, + supports_multiple_connections: bool | None = None, connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> ConnectedAccount: @@ -1340,6 +1712,7 @@ async def get_user_connected_account( user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -1358,6 +1731,7 @@ async def get_user_connected_account( k: v for k, v in { "organization_id": organization_id, + "supports_multiple_connections": supports_multiple_connections, "connected_account_id": connected_account_id, }.items() if v is not None @@ -1459,6 +1833,7 @@ async def update_user_connected_account( scopes: list[str] | None = None, state: ConnectedAccountInputState | str | None = None, organization_id: str | None = None, + supports_multiple_connections: bool | None = None, connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> ConnectedAccount: @@ -1475,6 +1850,7 @@ async def update_user_connected_account( scopes: The OAuth scopes granted for this connection. state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to update. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -1486,6 +1862,7 @@ async def update_user_connected_account( AuthenticationError: If the API key is invalid (401). NotFoundError: If the resource is not found (404). ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). RateLimitExceededError: If rate limited (429). ServerError: If the server returns a 5xx error. """ @@ -1504,6 +1881,7 @@ async def update_user_connected_account( k: v for k, v in { "organization_id": organization_id, + "supports_multiple_connections": supports_multiple_connections, "connected_account_id": connected_account_id, }.items() if v is not None @@ -1529,17 +1907,19 @@ async def delete_user_connected_account( slug: str, *, organization_id: str | None = None, + supports_multiple_connections: bool | None = None, connected_account_id: str | None = None, request_options: RequestOptions | None = None, ) -> None: """Delete a connected account - Disconnects WorkOS's account for the user, including removing any stored access and refresh tokens. The user will need to reauthorize if they want to reconnect. This does not revoke access on the provider side. + Disconnects WorkOS's account for the user, including removing any stored access and refresh tokens. The user will need to reauthorize if they want to reconnect. Access is not revoked on the provider side, except for the WorkOS OAuth provider, whose underlying AuthKit grant is revoked. Args: user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier. slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to delete. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -1555,6 +1935,7 @@ async def delete_user_connected_account( k: v for k, v in { "organization_id": organization_id, + "supports_multiple_connections": supports_multiple_connections, "connected_account_id": connected_account_id, }.items() if v is not None @@ -1577,6 +1958,7 @@ async def list_user_data_providers( user_id: str, *, organization_id: str | None = None, + supports_multiple_connections: bool | None = None, request_options: RequestOptions | None = None, ) -> DataIntegrationsListResponse: """List providers for a user @@ -1586,6 +1968,7 @@ async def list_user_data_providers( Args: user_id: A [User](https://workos.com/docs/reference/authkit/user) identifier to list providers and connected accounts for. organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to filter connections for a specific organization. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -1601,6 +1984,7 @@ async def list_user_data_providers( k: v for k, v in { "organization_id": organization_id, + "supports_multiple_connections": supports_multiple_connections, }.items() if v is not None } diff --git a/src/workos/pipes/models/__init__.py b/src/workos/pipes/models/__init__.py index e1493c0f5..1a6cff7ae 100644 --- a/src/workos/pipes/models/__init__.py +++ b/src/workos/pipes/models/__init__.py @@ -60,6 +60,7 @@ from .data_integrations_vend_credentials_request import ( DataIntegrationsVendCredentialsRequest as DataIntegrationsVendCredentialsRequest, ) +from .pipes_ownership import PipesOwnership as PipesOwnership from .update_custom_provider_definition import ( UpdateCustomProviderDefinition as UpdateCustomProviderDefinition, ) diff --git a/src/workos/pipes/models/api_key_installation.py b/src/workos/pipes/models/api_key_installation.py index 65219f4fe..6186d1bca 100644 --- a/src/workos/pipes/models/api_key_installation.py +++ b/src/workos/pipes/models/api_key_installation.py @@ -1,9 +1,41 @@ # This file is auto-generated by oagen. Do not edit. -from typing import TypeAlias +from __future__ import annotations -from .data_integrations_upsert_api_key_request import ( - DataIntegrationsUpsertApiKeyRequest, -) +from dataclasses import dataclass +from typing import Any -ApiKeyInstallation: TypeAlias = DataIntegrationsUpsertApiKeyRequest +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class ApiKeyInstallation: + """Api Key Installation model.""" + + secret: str + """The API key secret to store for the tenant.""" + user_id: str + """The User identifier the API key is installed for.""" + organization_id: str | None = None + """An Organization identifier to scope the installation to a specific organization.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> ApiKeyInstallation: + """Deserialize from a dictionary.""" + try: + return cls( + secret=data["secret"], + user_id=data["user_id"], + organization_id=data.get("organization_id"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("ApiKeyInstallation", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["secret"] = self.secret + result["user_id"] = self.user_id + if self.organization_id is not None: + result["organization_id"] = self.organization_id + return result diff --git a/src/workos/pipes/models/create_data_integration.py b/src/workos/pipes/models/create_data_integration.py index 580be7270..367096750 100644 --- a/src/workos/pipes/models/create_data_integration.py +++ b/src/workos/pipes/models/create_data_integration.py @@ -10,6 +10,9 @@ from workos.common.models.create_data_integration_auth_methods import ( CreateDataIntegrationAuthMethods, ) +from workos.common.models.create_data_integration_ownership import ( + CreateDataIntegrationOwnership, +) from .api_key_installation import ApiKeyInstallation from .custom_provider_definition import CustomProviderDefinition @@ -22,6 +25,8 @@ class CreateDataIntegration: provider: str """The provider to create a Data Integration for. For a built-in provider use its slug (e.g. `github`, `slack`). For a custom provider, this is the new provider slug and `custom_provider` must be supplied. A custom provider slug cannot shadow an existing global provider slug.""" + ownership: CreateDataIntegrationOwnership | None = None + """Who owns the Data Integration. `user` (the default) creates the integration users connect their own accounts to; `organization` creates the root organizations connect to. Ownership is fixed at creation, and one integration of each ownership may exist per provider. Independent of `credentials.type`.""" description: str | None = None """An optional description of the Data Integration.""" enabled: bool | None = None @@ -45,6 +50,9 @@ def from_dict(cls, data: dict[str, Any]) -> CreateDataIntegration: try: return cls( provider=data["provider"], + ownership=CreateDataIntegrationOwnership(_v_ownership) + if (_v_ownership := data.get("ownership")) is not None + else None, description=data.get("description"), enabled=data.get("enabled"), scopes=data.get("scopes"), @@ -76,6 +84,12 @@ def to_dict(self) -> dict[str, Any]: """Serialize to a dictionary.""" result: dict[str, Any] = {} result["provider"] = self.provider + if self.ownership is not None: + result["ownership"] = ( + self.ownership.value + if isinstance(self.ownership, Enum) + else self.ownership + ) if self.description is not None: result["description"] = self.description else: diff --git a/src/workos/pipes/models/data_integration.py b/src/workos/pipes/models/data_integration.py index 69dff813f..ffa61821a 100644 --- a/src/workos/pipes/models/data_integration.py +++ b/src/workos/pipes/models/data_integration.py @@ -11,6 +11,7 @@ from workos.common.models.data_integration_auth_methods import ( DataIntegrationAuthMethods, ) +from workos.common.models.data_integration_ownership import DataIntegrationOwnership from workos.common.models.data_integration_state import DataIntegrationState from .data_integration_credential import DataIntegrationCredential @@ -30,6 +31,8 @@ class DataIntegration: """The provider slug for this Data Integration.""" integration_type: str """The integration type derived from the provider.""" + ownership: DataIntegrationOwnership + """Who owns the Data Integration: `user` when users connect their own accounts, `organization` when organizations connect. Fixed at creation.""" description: str | None """An optional description of the Data Integration.""" enabled: bool @@ -64,6 +67,7 @@ def from_dict(cls, data: dict[str, Any]) -> DataIntegration: id=data["id"], slug=data["slug"], integration_type=data["integration_type"], + ownership=DataIntegrationOwnership(data["ownership"]), description=data["description"], enabled=data["enabled"], state=DataIntegrationState(data["state"]), @@ -102,6 +106,9 @@ def to_dict(self) -> dict[str, Any]: result["id"] = self.id result["slug"] = self.slug result["integration_type"] = self.integration_type + result["ownership"] = ( + self.ownership.value if isinstance(self.ownership, Enum) else self.ownership + ) if self.description is not None: result["description"] = self.description else: diff --git a/src/workos/pipes/models/data_integration_installation.py b/src/workos/pipes/models/data_integration_installation.py index 544b385f1..983f90365 100644 --- a/src/workos/pipes/models/data_integration_installation.py +++ b/src/workos/pipes/models/data_integration_installation.py @@ -14,10 +14,10 @@ class DataIntegrationInstallation: id: str """Unique identifier of the installation.""" - user_id: str - """The User the API key was installed for.""" + user_id: str | None + """The User the API key was installed for. Null on an `organization`-owned integration, whose installations belong to the organization.""" organization_id: str | None - """The Organization the installation is scoped to, or null when unscoped.""" + """The Organization the installation is scoped to (or owned by, on an `organization`-owned integration), or null when unscoped.""" api_key_last_4: str | None """The last four characters of the stored API key. The full key is never returned.""" @@ -38,7 +38,10 @@ def to_dict(self) -> dict[str, Any]: """Serialize to a dictionary.""" result: dict[str, Any] = {} result["id"] = self.id - result["user_id"] = self.user_id + if self.user_id is not None: + result["user_id"] = self.user_id + else: + result["user_id"] = None if self.organization_id is not None: result["organization_id"] = self.organization_id else: diff --git a/src/workos/pipes/models/data_integrations_get_user_token_request.py b/src/workos/pipes/models/data_integrations_get_user_token_request.py index 7c93c3b19..b0627b4e3 100644 --- a/src/workos/pipes/models/data_integrations_get_user_token_request.py +++ b/src/workos/pipes/models/data_integrations_get_user_token_request.py @@ -3,9 +3,13 @@ from __future__ import annotations from dataclasses import dataclass +from enum import Enum from typing import Any from workos._types import _raise_deserialize_error +from workos.common.models.data_integrations_get_user_token_request_connection_owner import ( + DataIntegrationsGetUserTokenRequestConnectionOwner, +) @dataclass(slots=True) @@ -13,11 +17,15 @@ class DataIntegrationsGetUserTokenRequest: """Data Integrations Get User Token Request model.""" user_id: str - """A [User](https://workos.com/docs/reference/authkit/user) identifier.""" + """A [User](https://workos.com/docs/reference/authkit/user) identifier. When `connection_owner` is `organization`, this is the user the credentials are vended on behalf of; they must be an active member of the organization.""" organization_id: str | None = None - """An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization.""" + """An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. Required when `connection_owner` is `organization`.""" connected_account_id: str | None = None """A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider.""" + connection_owner: DataIntegrationsGetUserTokenRequestConnectionOwner | None = None + """Which connection to vend from. `user` (the default) vends the user's own connection and requires `user_id`. `organization` vends the organization's shared connection and requires `organization_id`.""" + supports_multiple_connections: bool | None = None + """Set to `true` to use the plural connection contract. If no `connected_account_id` is supplied and several connections match, the request returns `account_selection_required`. When omitted or `false`, only the compatibility connection is considered.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsGetUserTokenRequest: @@ -27,6 +35,12 @@ def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsGetUserTokenRequest: user_id=data["user_id"], organization_id=data.get("organization_id"), connected_account_id=data.get("connected_account_id"), + connection_owner=DataIntegrationsGetUserTokenRequestConnectionOwner( + _v_connection_owner + ) + if (_v_connection_owner := data.get("connection_owner")) is not None + else None, + supports_multiple_connections=data.get("supports_multiple_connections"), ) except (KeyError, ValueError) as e: _raise_deserialize_error("DataIntegrationsGetUserTokenRequest", e) @@ -41,4 +55,12 @@ def to_dict(self) -> dict[str, Any]: result["organization_id"] = None if self.connected_account_id is not None: result["connected_account_id"] = self.connected_account_id + if self.connection_owner is not None: + result["connection_owner"] = ( + self.connection_owner.value + if isinstance(self.connection_owner, Enum) + else self.connection_owner + ) + if self.supports_multiple_connections is not None: + result["supports_multiple_connections"] = self.supports_multiple_connections return result diff --git a/src/workos/pipes/models/data_integrations_list_response_data.py b/src/workos/pipes/models/data_integrations_list_response_data.py index d4cbac8e9..cd02d5a59 100644 --- a/src/workos/pipes/models/data_integrations_list_response_data.py +++ b/src/workos/pipes/models/data_integrations_list_response_data.py @@ -46,9 +46,9 @@ class DataIntegrationsListResponseData: updated_at: str """The timestamp when the provider was last updated.""" connected_account: DataIntegrationsListResponseDataConnectedAccount | None - """The user's [connected account](https://workos.com/docs/reference/pipes/connected-account) for this provider, or `null` if the user has not connected.""" + """The user's compatibility [connected account](https://workos.com/docs/reference/pipes/connected-account) for this provider, or `null` when the compatibility slot is empty. This legacy field never selects a standard connection.""" connected_accounts: list[DataIntegrationsListResponseDataConnectedAccount] - """The user's connected accounts for this provider in the requested ownership context.""" + """The user's connected accounts for this provider in the requested ownership context. This contains only the compatibility connection unless `supports_multiple_connections` is `true`.""" auth_methods: list[DataIntegrationsListResponseDataAuthMethods] | None = None """The authentication methods supported by this provider (`oauth`, `api_key`, `client_credentials`, or a combination). Defaults to `["oauth"]` if absent.""" diff --git a/src/workos/pipes/models/data_integrations_upsert_api_key_request.py b/src/workos/pipes/models/data_integrations_upsert_api_key_request.py index 3172e3946..199f6cf8c 100644 --- a/src/workos/pipes/models/data_integrations_upsert_api_key_request.py +++ b/src/workos/pipes/models/data_integrations_upsert_api_key_request.py @@ -3,9 +3,13 @@ from __future__ import annotations from dataclasses import dataclass +from enum import Enum from typing import Any from workos._types import _raise_deserialize_error +from workos.common.models.data_integrations_upsert_api_key_request_connection_owner import ( + DataIntegrationsUpsertApiKeyRequestConnectionOwner, +) @dataclass(slots=True) @@ -17,7 +21,11 @@ class DataIntegrationsUpsertApiKeyRequest: secret: str """The API key secret to store for this integration.""" organization_id: str | None = None - """An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization.""" + """An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. Required when `connection_owner` is `organization`.""" + connected_account_id: str | None = None + """A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to rotate a specific existing connection.""" + connection_owner: DataIntegrationsUpsertApiKeyRequestConnectionOwner | None = None + """Whose connection to create or rotate. `user` (the default) addresses the connection owned by `user_id`. `organization` addresses the connection shared by every member of `organization_id`; `user_id` then identifies the member performing the request and must be an active member of the organization.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsUpsertApiKeyRequest: @@ -27,6 +35,12 @@ def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsUpsertApiKeyRequest: user_id=data["user_id"], secret=data["secret"], organization_id=data.get("organization_id"), + connected_account_id=data.get("connected_account_id"), + connection_owner=DataIntegrationsUpsertApiKeyRequestConnectionOwner( + _v_connection_owner + ) + if (_v_connection_owner := data.get("connection_owner")) is not None + else None, ) except (KeyError, ValueError) as e: _raise_deserialize_error("DataIntegrationsUpsertApiKeyRequest", e) @@ -38,4 +52,12 @@ def to_dict(self) -> dict[str, Any]: result["secret"] = self.secret if self.organization_id is not None: result["organization_id"] = self.organization_id + if self.connected_account_id is not None: + result["connected_account_id"] = self.connected_account_id + if self.connection_owner is not None: + result["connection_owner"] = ( + self.connection_owner.value + if isinstance(self.connection_owner, Enum) + else self.connection_owner + ) return result diff --git a/src/workos/pipes/models/data_integrations_upsert_client_credentials_request.py b/src/workos/pipes/models/data_integrations_upsert_client_credentials_request.py index ea1e0e76d..56efd7ba3 100644 --- a/src/workos/pipes/models/data_integrations_upsert_client_credentials_request.py +++ b/src/workos/pipes/models/data_integrations_upsert_client_credentials_request.py @@ -3,9 +3,13 @@ from __future__ import annotations from dataclasses import dataclass +from enum import Enum from typing import Any from workos._types import _raise_deserialize_error +from workos.common.models.data_integrations_upsert_client_credentials_request_connection_owner import ( + DataIntegrationsUpsertClientCredentialsRequestConnectionOwner, +) @dataclass(slots=True) @@ -19,7 +23,13 @@ class DataIntegrationsUpsertClientCredentialsRequest: client_secret: str """The OAuth client secret to store for this integration.""" organization_id: str | None = None - """An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization.""" + """An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. Required when `connection_owner` is `organization`.""" + connected_account_id: str | None = None + """A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to rotate a specific existing connection.""" + connection_owner: ( + DataIntegrationsUpsertClientCredentialsRequestConnectionOwner | None + ) = None + """Whose connection to create or rotate. `user` (the default) addresses the connection owned by `user_id`. `organization` addresses the connection shared by every member of `organization_id`; `user_id` then identifies the member performing the request and must be an active member of the organization.""" config: dict[str, str] | None = None """Provider-specific configuration values collected for this installation, keyed by the provider's config field descriptors.""" @@ -34,6 +44,12 @@ def from_dict( client_id=data["client_id"], client_secret=data["client_secret"], organization_id=data.get("organization_id"), + connected_account_id=data.get("connected_account_id"), + connection_owner=DataIntegrationsUpsertClientCredentialsRequestConnectionOwner( + _v_connection_owner + ) + if (_v_connection_owner := data.get("connection_owner")) is not None + else None, config=data.get("config"), ) except (KeyError, ValueError) as e: @@ -49,6 +65,14 @@ def to_dict(self) -> dict[str, Any]: result["client_secret"] = self.client_secret if self.organization_id is not None: result["organization_id"] = self.organization_id + if self.connected_account_id is not None: + result["connected_account_id"] = self.connected_account_id + if self.connection_owner is not None: + result["connection_owner"] = ( + self.connection_owner.value + if isinstance(self.connection_owner, Enum) + else self.connection_owner + ) if self.config is not None: result["config"] = self.config return result diff --git a/src/workos/pipes/models/data_integrations_vend_credentials_request.py b/src/workos/pipes/models/data_integrations_vend_credentials_request.py index 941de6e35..3afebbe07 100644 --- a/src/workos/pipes/models/data_integrations_vend_credentials_request.py +++ b/src/workos/pipes/models/data_integrations_vend_credentials_request.py @@ -3,9 +3,13 @@ from __future__ import annotations from dataclasses import dataclass +from enum import Enum from typing import Any from workos._types import _raise_deserialize_error +from workos.common.models.data_integrations_vend_credentials_request_connection_owner import ( + DataIntegrationsVendCredentialsRequestConnectionOwner, +) @dataclass(slots=True) @@ -13,11 +17,17 @@ class DataIntegrationsVendCredentialsRequest: """Data Integrations Vend Credentials Request model.""" user_id: str - """A [User](https://workos.com/docs/reference/authkit/user) identifier.""" + """A [User](https://workos.com/docs/reference/authkit/user) identifier. When `connection_owner` is `organization`, this is the user the credentials are vended on behalf of; they must be an active member of the organization.""" organization_id: str | None = None - """An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization.""" + """An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter to scope the connection to a specific organization. Required when `connection_owner` is `organization`.""" connected_account_id: str | None = None """A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the user has several for this provider.""" + connection_owner: DataIntegrationsVendCredentialsRequestConnectionOwner | None = ( + None + ) + """Which connection to vend from. `user` (the default) vends the user's own connection and requires `user_id`. `organization` vends the organization's shared connection and requires `organization_id`.""" + supports_multiple_connections: bool | None = None + """Set to `true` to use the plural connection contract. If no `connected_account_id` is supplied and several connections match, the request returns `account_selection_required`. When omitted or `false`, only the compatibility connection is considered.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsVendCredentialsRequest: @@ -27,6 +37,12 @@ def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsVendCredentialsReque user_id=data["user_id"], organization_id=data.get("organization_id"), connected_account_id=data.get("connected_account_id"), + connection_owner=DataIntegrationsVendCredentialsRequestConnectionOwner( + _v_connection_owner + ) + if (_v_connection_owner := data.get("connection_owner")) is not None + else None, + supports_multiple_connections=data.get("supports_multiple_connections"), ) except (KeyError, ValueError) as e: _raise_deserialize_error("DataIntegrationsVendCredentialsRequest", e) @@ -39,4 +55,12 @@ def to_dict(self) -> dict[str, Any]: result["organization_id"] = self.organization_id if self.connected_account_id is not None: result["connected_account_id"] = self.connected_account_id + if self.connection_owner is not None: + result["connection_owner"] = ( + self.connection_owner.value + if isinstance(self.connection_owner, Enum) + else self.connection_owner + ) + if self.supports_multiple_connections is not None: + result["supports_multiple_connections"] = self.supports_multiple_connections return result diff --git a/src/workos/pipes/models/pipes_ownership.py b/src/workos/pipes/models/pipes_ownership.py new file mode 100644 index 000000000..2abef5040 --- /dev/null +++ b/src/workos/pipes/models/pipes_ownership.py @@ -0,0 +1,21 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from workos.common.models.create_data_integration_ownership import ( + CreateDataIntegrationOwnership as PipesOwnership, + ) +else: + + def __getattr__(name: str): + if name == "PipesOwnership": + from workos.common.models.create_data_integration_ownership import ( + CreateDataIntegrationOwnership, + ) + + return CreateDataIntegrationOwnership + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +__all__ = ["PipesOwnership"] diff --git a/src/workos/user_management/_resource.py b/src/workos/user_management/_resource.py index 53629ec78..9bb150c8d 100644 --- a/src/workos/user_management/_resource.py +++ b/src/workos/user_management/_resource.py @@ -35,6 +35,7 @@ ) from .models import ( AuthenticateResponse, + AuthkitOAuthResource, AuthorizationCodeSessionAuthenticateRequest, AuthorizedConnectApplicationListData, CORSOriginResponse, @@ -816,6 +817,119 @@ def revoke_session( request_options=request_options, ) + def list_authkit_oauth_resources( + self, + *, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: PaginationOrder | str | None = "desc", + request_options: RequestOptions | None = None, + ) -> SyncPage[AuthkitOAuthResource]: + """List MCP resource indicators + + Lists the MCP resource indicators configured for an environment. + + Args: + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. + order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SyncPage[AuthkitOAuthResource] + + Raises: + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + }.items() + if v is not None + } + return self._client.request_page( + method="get", + path=("user_management", "authkit_oauth_resources"), + model=AuthkitOAuthResource, + params=params, + request_options=request_options, + ) + + def create_authkit_oauth_resource( + self, + *, + uri: str, + default: bool | None = None, + request_options: RequestOptions | None = None, + ) -> AuthkitOAuthResource: + """Create an MCP resource indicator + + Adds an MCP resource indicator (RFC 8707) to an environment, leaving any others in place. + + Args: + uri: The resource URI. May be a wildcard pattern with a single `*` in the leftmost hostname label, where enabled for the environment. + default: Whether the resource being created becomes the environment default, clearing any previous default. Applies at creation only — this API has no update endpoint yet, so changing the default on an existing resource is done from the dashboard. A wildcard pattern cannot be the default. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AuthkitOAuthResource + + Raises: + AuthenticationError: If the API key is invalid (401). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "uri": uri, + "default": default, + }.items() + if v is not None + } + return self._client.request( + method="post", + path=("user_management", "authkit_oauth_resources"), + body=body, + model=AuthkitOAuthResource, + request_options=request_options, + ) + + def delete_authkit_oauth_resource( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an MCP resource indicator + + Removes an MCP resource indicator from an environment. Any application consents granted against it are removed too. + + Args: + id: The ID of the MCP resource indicator to delete. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + self._client.request( + method="delete", + path=("user_management", "authkit_oauth_resources", str(id)), + request_options=request_options, + ) + def list_cors_origins( self, *, @@ -3455,6 +3569,119 @@ async def revoke_session( request_options=request_options, ) + async def list_authkit_oauth_resources( + self, + *, + limit: int | None = None, + before: str | None = None, + after: str | None = None, + order: PaginationOrder | str | None = "desc", + request_options: RequestOptions | None = None, + ) -> AsyncPage[AuthkitOAuthResource]: + """List MCP resource indicators + + Lists the MCP resource indicators configured for an environment. + + Args: + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. + order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AsyncPage[AuthkitOAuthResource] + + Raises: + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + }.items() + if v is not None + } + return await self._client.request_page( + method="get", + path=("user_management", "authkit_oauth_resources"), + model=AuthkitOAuthResource, + params=params, + request_options=request_options, + ) + + async def create_authkit_oauth_resource( + self, + *, + uri: str, + default: bool | None = None, + request_options: RequestOptions | None = None, + ) -> AuthkitOAuthResource: + """Create an MCP resource indicator + + Adds an MCP resource indicator (RFC 8707) to an environment, leaving any others in place. + + Args: + uri: The resource URI. May be a wildcard pattern with a single `*` in the leftmost hostname label, where enabled for the environment. + default: Whether the resource being created becomes the environment default, clearing any previous default. Applies at creation only — this API has no update endpoint yet, so changing the default on an existing resource is done from the dashboard. A wildcard pattern cannot be the default. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AuthkitOAuthResource + + Raises: + AuthenticationError: If the API key is invalid (401). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "uri": uri, + "default": default, + }.items() + if v is not None + } + return await self._client.request( + method="post", + path=("user_management", "authkit_oauth_resources"), + body=body, + model=AuthkitOAuthResource, + request_options=request_options, + ) + + async def delete_authkit_oauth_resource( + self, + id: str, + *, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an MCP resource indicator + + Removes an MCP resource indicator from an environment. Any application consents granted against it are removed too. + + Args: + id: The ID of the MCP resource indicator to delete. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + await self._client.request( + method="delete", + path=("user_management", "authkit_oauth_resources", str(id)), + request_options=request_options, + ) + async def list_cors_origins( self, *, diff --git a/src/workos/user_management/models/__init__.py b/src/workos/user_management/models/__init__.py index b5f364734..110ab1107 100644 --- a/src/workos/user_management/models/__init__.py +++ b/src/workos/user_management/models/__init__.py @@ -15,6 +15,7 @@ from .authenticate_response_oauth_token import ( AuthenticateResponseOAuthToken as AuthenticateResponseOAuthToken, ) +from .authkit_oauth_resource import AuthkitOAuthResource as AuthkitOAuthResource from .authorization_code_session_authenticate_request import ( AuthorizationCodeSessionAuthenticateRequest as AuthorizationCodeSessionAuthenticateRequest, ) @@ -23,6 +24,9 @@ ) from .confirm_email_change import ConfirmEmailChange as ConfirmEmailChange from .cors_origin_response import CORSOriginResponse as CORSOriginResponse +from .create_authkit_oauth_resource import ( + CreateAuthkitOAuthResource as CreateAuthkitOAuthResource, +) from .create_cors_origin import CreateCORSOrigin as CreateCORSOrigin from .create_magic_code_and_return import ( CreateMagicCodeAndReturn as CreateMagicCodeAndReturn, diff --git a/src/workos/user_management/models/authkit_oauth_resource.py b/src/workos/user_management/models/authkit_oauth_resource.py new file mode 100644 index 000000000..e25fff779 --- /dev/null +++ b/src/workos/user_management/models/authkit_oauth_resource.py @@ -0,0 +1,52 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Literal + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class AuthkitOAuthResource: + """Authkit O Auth Resource model.""" + + object: Literal["authkit_oauth_resource"] + """The object type.""" + id: str + """The ID of the MCP resource indicator.""" + uri: str + """The resource URI.""" + default: bool + """Whether this is the default MCP resource indicator for the environment.""" + created_at: str + """The timestamp when the MCP resource indicator was created.""" + updated_at: str + """The timestamp when the MCP resource indicator was last updated.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> AuthkitOAuthResource: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "authkit_oauth_resource"), + id=data["id"], + uri=data["uri"], + default=data["default"], + created_at=data["created_at"], + updated_at=data["updated_at"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("AuthkitOAuthResource", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["uri"] = self.uri + result["default"] = self.default + result["created_at"] = self.created_at + result["updated_at"] = self.updated_at + return result diff --git a/src/workos/user_management/models/create_authkit_oauth_resource.py b/src/workos/user_management/models/create_authkit_oauth_resource.py new file mode 100644 index 000000000..2db9b4bcf --- /dev/null +++ b/src/workos/user_management/models/create_authkit_oauth_resource.py @@ -0,0 +1,37 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class CreateAuthkitOAuthResource: + """Create Authkit O Auth Resource model.""" + + uri: str + """The resource URI. May be a wildcard pattern with a single `*` in the leftmost hostname label, where enabled for the environment.""" + default: bool | None = None + """Whether the resource being created becomes the environment default, clearing any previous default. Applies at creation only — this API has no update endpoint yet, so changing the default on an existing resource is done from the dashboard. A wildcard pattern cannot be the default.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> CreateAuthkitOAuthResource: + """Deserialize from a dictionary.""" + try: + return cls( + uri=data["uri"], + default=data.get("default"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateAuthkitOAuthResource", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["uri"] = self.uri + if self.default is not None: + result["default"] = self.default + return result diff --git a/tests/fixtures/account_selection_required_error.json b/tests/fixtures/account_selection_required_error.json new file mode 100644 index 000000000..f946d2933 --- /dev/null +++ b/tests/fixtures/account_selection_required_error.json @@ -0,0 +1,4 @@ +{ + "code": "account_selection_required", + "message": "Several connected accounts match this user for this provider. Name one with a connected account id." +} diff --git a/tests/fixtures/authkit_oauth_resource.json b/tests/fixtures/authkit_oauth_resource.json new file mode 100644 index 000000000..34219a5db --- /dev/null +++ b/tests/fixtures/authkit_oauth_resource.json @@ -0,0 +1,8 @@ +{ + "object": "authkit_oauth_resource", + "id": "authkit_oauth_resource_01EHZNVPK3SFK441A1RGBFSHRT", + "uri": "https://api.example.com", + "default": false, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/create_authkit_oauth_resource.json b/tests/fixtures/create_authkit_oauth_resource.json new file mode 100644 index 000000000..29d4f9d81 --- /dev/null +++ b/tests/fixtures/create_authkit_oauth_resource.json @@ -0,0 +1,4 @@ +{ + "uri": "https://api.example.com", + "default": false +} diff --git a/tests/fixtures/create_data_integration.json b/tests/fixtures/create_data_integration.json index 311643a1d..019066f2d 100644 --- a/tests/fixtures/create_data_integration.json +++ b/tests/fixtures/create_data_integration.json @@ -1,5 +1,6 @@ { "provider": "github", + "ownership": "user", "description": "Production GitHub app", "enabled": true, "scopes": [ diff --git a/tests/fixtures/data_integration.json b/tests/fixtures/data_integration.json index 5a4a1532c..c61f861f5 100644 --- a/tests/fixtures/data_integration.json +++ b/tests/fixtures/data_integration.json @@ -3,6 +3,7 @@ "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "slug": "github", "integration_type": "github", + "ownership": "user", "description": "Production GitHub app", "enabled": true, "state": "valid", diff --git a/tests/fixtures/data_integrations_get_user_token_request.json b/tests/fixtures/data_integrations_get_user_token_request.json index 66f0412ec..9b96a12f0 100644 --- a/tests/fixtures/data_integrations_get_user_token_request.json +++ b/tests/fixtures/data_integrations_get_user_token_request.json @@ -1,5 +1,7 @@ { "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT", - "connected_account_id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + "connected_account_id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_owner": "user", + "supports_multiple_connections": true } diff --git a/tests/fixtures/data_integrations_upsert_api_key_request.json b/tests/fixtures/data_integrations_upsert_api_key_request.json index bf0bd81be..45952523c 100644 --- a/tests/fixtures/data_integrations_upsert_api_key_request.json +++ b/tests/fixtures/data_integrations_upsert_api_key_request.json @@ -1,5 +1,7 @@ { "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT", + "connected_account_id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_owner": "user", "secret": "sk-1234567890abcdef" } diff --git a/tests/fixtures/data_integrations_upsert_client_credentials_request.json b/tests/fixtures/data_integrations_upsert_client_credentials_request.json index 1dc0fac06..9497d49c2 100644 --- a/tests/fixtures/data_integrations_upsert_client_credentials_request.json +++ b/tests/fixtures/data_integrations_upsert_client_credentials_request.json @@ -1,6 +1,8 @@ { "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT", + "connected_account_id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_owner": "user", "client_id": "3MVG9...", "client_secret": "shhh-secret", "config": { diff --git a/tests/fixtures/data_integrations_vend_credentials_request.json b/tests/fixtures/data_integrations_vend_credentials_request.json index 66f0412ec..9b96a12f0 100644 --- a/tests/fixtures/data_integrations_vend_credentials_request.json +++ b/tests/fixtures/data_integrations_vend_credentials_request.json @@ -1,5 +1,7 @@ { "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT", - "connected_account_id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + "connected_account_id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_owner": "user", + "supports_multiple_connections": true } diff --git a/tests/fixtures/list_authkit_oauth_resource.json b/tests/fixtures/list_authkit_oauth_resource.json new file mode 100644 index 000000000..4d79551ca --- /dev/null +++ b/tests/fixtures/list_authkit_oauth_resource.json @@ -0,0 +1,16 @@ +{ + "data": [ + { + "object": "authkit_oauth_resource", + "id": "authkit_oauth_resource_01EHZNVPK3SFK441A1RGBFSHRT", + "uri": "https://api.example.com", + "default": false, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + } + ], + "list_metadata": { + "before": null, + "after": null + } +} diff --git a/tests/fixtures/list_data_integration.json b/tests/fixtures/list_data_integration.json index d41b44b8f..b6860c1b0 100644 --- a/tests/fixtures/list_data_integration.json +++ b/tests/fixtures/list_data_integration.json @@ -5,6 +5,7 @@ "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "slug": "github", "integration_type": "github", + "ownership": "user", "description": "Production GitHub app", "enabled": true, "state": "valid", diff --git a/tests/test_agents.py b/tests/test_agents.py index bd2519cca..cc29fdd00 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -279,6 +279,7 @@ def test_list_sessions_encodes_query_params(self, workos, httpx_mock): before="cursor before", after="cursor/after", order=PaginationOrder("value_order"), + organization_id="value organization_id/test", agent_blueprint_id="value agent_blueprint_id/test", agent_instance_id="value agent_instance_id/test", ) @@ -287,6 +288,7 @@ def test_list_sessions_encodes_query_params(self, workos, httpx_mock): assert request.url.params["before"] == "cursor before" assert request.url.params["after"] == "cursor/after" assert request.url.params["order"] == "value_order" + assert request.url.params["organization_id"] == "value organization_id/test" assert ( request.url.params["agent_blueprint_id"] == "value agent_blueprint_id/test" ) @@ -623,6 +625,7 @@ async def test_list_sessions_encodes_query_params(self, async_workos, httpx_mock before="cursor before", after="cursor/after", order=PaginationOrder("value_order"), + organization_id="value organization_id/test", agent_blueprint_id="value agent_blueprint_id/test", agent_instance_id="value agent_instance_id/test", ) @@ -631,6 +634,7 @@ async def test_list_sessions_encodes_query_params(self, async_workos, httpx_mock assert request.url.params["before"] == "cursor before" assert request.url.params["after"] == "cursor/after" assert request.url.params["order"] == "value_order" + assert request.url.params["organization_id"] == "value organization_id/test" assert ( request.url.params["agent_blueprint_id"] == "value agent_blueprint_id/test" ) diff --git a/tests/test_common_models_round_trip.py b/tests/test_common_models_round_trip.py index c9476f4e5..8498f807a 100644 --- a/tests/test_common_models_round_trip.py +++ b/tests/test_common_models_round_trip.py @@ -5,6 +5,7 @@ from tests.generated_helpers import load_fixture from workos.common.models import ( AccessTokenAgentRegistrationCredentialIssuedDataDetail, + AccountSelectionRequiredError, ActionAuthenticationDenied, ActionAuthenticationDeniedData, ActionUserRegistrationDenied, @@ -14942,6 +14943,24 @@ def test_waitlist_user_denied_omits_absent_optional_non_nullable_fields(self): serialized = instance.to_dict() assert "context" not in serialized + def test_account_selection_required_error_round_trip(self): + data = load_fixture("account_selection_required_error.json") + instance = AccountSelectionRequiredError.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AccountSelectionRequiredError.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_account_selection_required_error_minimal_payload(self): + data = { + "code": "account_selection_required", + "message": "Several connected accounts match this user for this provider. Name one with a connected account id.", + } + instance = AccountSelectionRequiredError.from_dict(data) + serialized = instance.to_dict() + assert serialized["code"] == data["code"] + assert serialized["message"] == data["message"] + def test_auth_method_mismatch_error_round_trip(self): data = load_fixture("auth_method_mismatch_error.json") instance = AuthMethodMismatchError.from_dict(data) diff --git a/tests/test_pipes.py b/tests/test_pipes.py index f2b194941..9955f1154 100644 --- a/tests/test_pipes.py +++ b/tests/test_pipes.py @@ -25,6 +25,7 @@ DataIntegrationAuthorizeUrlResponse, DataIntegrationCredentialsResponse, DataIntegrationsListResponse, + PipesOwnership, ) @@ -51,12 +52,14 @@ def test_list_data_integrations_encodes_query_params(self, workos, httpx_mock): before="cursor before", after="cursor/after", order=PaginationOrder("value_order"), + ownership=PipesOwnership("user"), ) request = httpx_mock.get_request() assert request.url.params["limit"] == "10" assert request.url.params["before"] == "cursor before" assert request.url.params["after"] == "cursor/after" assert request.url.params["order"] == "value_order" + assert request.url.params["ownership"] == "user" def test_create_data_integration(self, workos, httpx_mock): httpx_mock.add_response( @@ -178,6 +181,38 @@ def test_create_data_integration_credential(self, workos, httpx_mock): body = json.loads(request.content) assert body["user_id"] == "test_user_id" + def test_list_data_integration_organization(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("data_integration.json"), + ) + result = workos.pipes.list_data_integration_organization("test_slug") + assert isinstance(result, DataIntegration) + assert result.object == "data_integration" + assert result.id == "data_integration_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/data-integrations/test_slug/organization") + + def test_update_data_integration_organization(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("data_integration.json"), + ) + result = workos.pipes.update_data_integration_organization("test_slug") + assert isinstance(result, DataIntegration) + assert result.object == "data_integration" + assert result.id == "data_integration_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "PUT" + assert request.url.path.endswith("/data-integrations/test_slug/organization") + + def test_delete_data_integration_organization(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = workos.pipes.delete_data_integration_organization("test_slug") + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith("/data-integrations/test_slug/organization") + def test_get_access_token(self, workos, httpx_mock): httpx_mock.add_response( json=load_fixture("data_integration_access_token_response.json"), @@ -212,10 +247,12 @@ def test_get_user_connected_account_encodes_query_params(self, workos, httpx_moc "test_user_id", "test_slug", organization_id="value organization_id/test", + supports_multiple_connections=True, connected_account_id="value connected_account_id/test", ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert request.url.params["supports_multiple_connections"] == "true" assert ( request.url.params["connected_account_id"] == "value connected_account_id/test" @@ -267,10 +304,12 @@ def test_update_user_connected_account_encodes_query_params( "test_user_id", "test_slug", organization_id="value organization_id/test", + supports_multiple_connections=True, connected_account_id="value connected_account_id/test", ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert request.url.params["supports_multiple_connections"] == "true" assert ( request.url.params["connected_account_id"] == "value connected_account_id/test" @@ -294,10 +333,12 @@ def test_delete_user_connected_account_encodes_query_params( "test_user_id", "test_slug", organization_id="value organization_id/test", + supports_multiple_connections=True, connected_account_id="value connected_account_id/test", ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert request.url.params["supports_multiple_connections"] == "true" assert ( request.url.params["connected_account_id"] == "value connected_account_id/test" @@ -321,10 +362,13 @@ def test_list_user_data_providers_encodes_query_params(self, workos, httpx_mock) json=load_fixture("data_integrations_list_response.json") ) workos.pipes.list_user_data_providers( - "test_user_id", organization_id="value organization_id/test" + "test_user_id", + organization_id="value organization_id/test", + supports_multiple_connections=True, ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert request.url.params["supports_multiple_connections"] == "true" def test_list_data_integrations_with_request_options(self, workos, httpx_mock): httpx_mock.add_response(json={"data": [], "list_metadata": {}}) @@ -428,12 +472,14 @@ async def test_list_data_integrations_encodes_query_params( before="cursor before", after="cursor/after", order=PaginationOrder("value_order"), + ownership=PipesOwnership("user"), ) request = httpx_mock.get_request() assert request.url.params["limit"] == "10" assert request.url.params["before"] == "cursor before" assert request.url.params["after"] == "cursor/after" assert request.url.params["order"] == "value_order" + assert request.url.params["ownership"] == "user" @pytest.mark.asyncio async def test_create_data_integration(self, async_workos, httpx_mock): @@ -544,6 +590,43 @@ async def test_create_data_integration_credential(self, async_workos, httpx_mock assert request.method == "POST" assert request.url.path.endswith("/data-integrations/test_slug/credentials") + @pytest.mark.asyncio + async def test_list_data_integration_organization(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("data_integration.json")) + result = await async_workos.pipes.list_data_integration_organization( + "test_slug" + ) + assert isinstance(result, DataIntegration) + assert result.object == "data_integration" + assert result.id == "data_integration_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/data-integrations/test_slug/organization") + + @pytest.mark.asyncio + async def test_update_data_integration_organization(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("data_integration.json")) + result = await async_workos.pipes.update_data_integration_organization( + "test_slug" + ) + assert isinstance(result, DataIntegration) + assert result.object == "data_integration" + assert result.id == "data_integration_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "PUT" + assert request.url.path.endswith("/data-integrations/test_slug/organization") + + @pytest.mark.asyncio + async def test_delete_data_integration_organization(self, async_workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = await async_workos.pipes.delete_data_integration_organization( + "test_slug" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith("/data-integrations/test_slug/organization") + @pytest.mark.asyncio async def test_get_access_token(self, async_workos, httpx_mock): httpx_mock.add_response( @@ -583,10 +666,12 @@ async def test_get_user_connected_account_encodes_query_params( "test_user_id", "test_slug", organization_id="value organization_id/test", + supports_multiple_connections=True, connected_account_id="value connected_account_id/test", ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert request.url.params["supports_multiple_connections"] == "true" assert ( request.url.params["connected_account_id"] == "value connected_account_id/test" @@ -642,10 +727,12 @@ async def test_update_user_connected_account_encodes_query_params( "test_user_id", "test_slug", organization_id="value organization_id/test", + supports_multiple_connections=True, connected_account_id="value connected_account_id/test", ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert request.url.params["supports_multiple_connections"] == "true" assert ( request.url.params["connected_account_id"] == "value connected_account_id/test" @@ -673,10 +760,12 @@ async def test_delete_user_connected_account_encodes_query_params( "test_user_id", "test_slug", organization_id="value organization_id/test", + supports_multiple_connections=True, connected_account_id="value connected_account_id/test", ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert request.url.params["supports_multiple_connections"] == "true" assert ( request.url.params["connected_account_id"] == "value connected_account_id/test" @@ -704,10 +793,13 @@ async def test_list_user_data_providers_encodes_query_params( json=load_fixture("data_integrations_list_response.json") ) await async_workos.pipes.list_user_data_providers( - "test_user_id", organization_id="value organization_id/test" + "test_user_id", + organization_id="value organization_id/test", + supports_multiple_connections=True, ) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + assert request.url.params["supports_multiple_connections"] == "true" @pytest.mark.asyncio async def test_list_data_integrations_with_request_options( diff --git a/tests/test_pipes_models_round_trip.py b/tests/test_pipes_models_round_trip.py index be400f2ab..cab9c812d 100644 --- a/tests/test_pipes_models_round_trip.py +++ b/tests/test_pipes_models_round_trip.py @@ -229,6 +229,7 @@ def test_data_integration_minimal_payload(self): "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "slug": "github", "integration_type": "github", + "ownership": "user", "description": None, "enabled": True, "state": "valid", @@ -248,6 +249,7 @@ def test_data_integration_minimal_payload(self): assert serialized["id"] == data["id"] assert serialized["slug"] == data["slug"] assert serialized["integration_type"] == data["integration_type"] + assert serialized["ownership"] == data["ownership"] assert serialized["description"] == data["description"] assert serialized["enabled"] == data["enabled"] assert serialized["state"] == data["state"] @@ -267,6 +269,7 @@ def test_data_integration_preserves_nullable_fields(self): "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "slug": "github", "integration_type": "github", + "ownership": "user", "description": None, "enabled": True, "state": "valid", @@ -294,9 +297,10 @@ def test_data_integration_round_trips_unknown_enum_values(self): "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "slug": "github", "integration_type": "github", + "ownership": "unexpected_data_integration_ownership", "description": "Production GitHub app", "enabled": True, - "state": "unexpected_data_integration_state", + "state": "valid", "scopes": ["repo", "read:org"], "redirect_uri": "https://api.workos.com/data-integrations/github/dik_01EHZNVPK3SFK441A1RGBFSHRT/callback", "auth_methods": ["oauth"], @@ -909,7 +913,7 @@ def test_data_integration_installation_round_trip(self): def test_data_integration_installation_minimal_payload(self): data = { "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "user_id": None, "organization_id": None, "api_key_last_4": None, } @@ -923,12 +927,13 @@ def test_data_integration_installation_minimal_payload(self): def test_data_integration_installation_preserves_nullable_fields(self): data = { "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "user_id": None, "organization_id": None, "api_key_last_4": None, } instance = DataIntegrationInstallation.from_dict(data) serialized = instance.to_dict() + assert serialized["user_id"] is None assert serialized["organization_id"] is None assert serialized["api_key_last_4"] is None diff --git a/tests/test_user_management.py b/tests/test_user_management.py index 3a76684d9..ca1faa4fb 100644 --- a/tests/test_user_management.py +++ b/tests/test_user_management.py @@ -23,6 +23,7 @@ from workos.user_management._resource import PasswordPlaintext from workos.user_management.models import ( AuthenticateResponse, + AuthkitOAuthResource, AuthorizedConnectApplicationListData, CORSOriginResponse, DeviceAuthorizationResponse, @@ -147,6 +148,61 @@ def test_revoke_session(self, workos, httpx_mock): assert request.method == "POST" assert request.url.path.endswith("/user_management/sessions/revoke") + def test_list_authkit_oauth_resources(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("list_authkit_oauth_resource.json"), + ) + page = workos.user_management.list_authkit_oauth_resources() + assert isinstance(page, SyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], AuthkitOAuthResource) + + def test_list_authkit_oauth_resources_empty_page(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = workos.user_management.list_authkit_oauth_resources() + assert isinstance(page, SyncPage) + assert page.data == [] + + def test_list_authkit_oauth_resources_encodes_query_params( + self, workos, httpx_mock + ): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + workos.user_management.list_authkit_oauth_resources( + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + + def test_create_authkit_oauth_resource(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("authkit_oauth_resource.json"), + ) + result = workos.user_management.create_authkit_oauth_resource(uri="test_uri") + assert isinstance(result, AuthkitOAuthResource) + assert result.object == "authkit_oauth_resource" + assert result.id == "authkit_oauth_resource_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/user_management/authkit_oauth_resources") + body = json.loads(request.content) + assert body["uri"] == "test_uri" + + def test_delete_authkit_oauth_resource(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = workos.user_management.delete_authkit_oauth_resource("test_id") + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/user_management/authkit_oauth_resources/test_id" + ) + def test_list_cors_origins(self, workos, httpx_mock): httpx_mock.add_response( json=load_fixture("list_cors_origin_response.json"), @@ -1149,6 +1205,66 @@ async def test_revoke_session(self, async_workos, httpx_mock): assert request.method == "POST" assert request.url.path.endswith("/user_management/sessions/revoke") + @pytest.mark.asyncio + async def test_list_authkit_oauth_resources(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("list_authkit_oauth_resource.json")) + page = await async_workos.user_management.list_authkit_oauth_resources() + assert isinstance(page, AsyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], AuthkitOAuthResource) + + @pytest.mark.asyncio + async def test_list_authkit_oauth_resources_empty_page( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = await async_workos.user_management.list_authkit_oauth_resources() + assert isinstance(page, AsyncPage) + assert page.data == [] + + @pytest.mark.asyncio + async def test_list_authkit_oauth_resources_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + await async_workos.user_management.list_authkit_oauth_resources( + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + + @pytest.mark.asyncio + async def test_create_authkit_oauth_resource(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("authkit_oauth_resource.json")) + result = await async_workos.user_management.create_authkit_oauth_resource( + uri="test_uri" + ) + assert isinstance(result, AuthkitOAuthResource) + assert result.object == "authkit_oauth_resource" + assert result.id == "authkit_oauth_resource_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/user_management/authkit_oauth_resources") + + @pytest.mark.asyncio + async def test_delete_authkit_oauth_resource(self, async_workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = await async_workos.user_management.delete_authkit_oauth_resource( + "test_id" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/user_management/authkit_oauth_resources/test_id" + ) + @pytest.mark.asyncio async def test_list_cors_origins(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("list_cors_origin_response.json")) diff --git a/tests/test_user_management_models_round_trip.py b/tests/test_user_management_models_round_trip.py index 07a63f681..c84fdf7ed 100644 --- a/tests/test_user_management_models_round_trip.py +++ b/tests/test_user_management_models_round_trip.py @@ -7,6 +7,7 @@ AuthenticateResponse, AuthenticateResponseImpersonator, AuthenticateResponseOAuthToken, + AuthkitOAuthResource, AuthorizedConnectApplicationListData, CORSOriginResponse, DeviceAuthorizationResponse, @@ -200,6 +201,32 @@ def test_send_radar_sms_challenge_response_minimal_payload(self): assert serialized["verification_id"] == data["verification_id"] assert serialized["phone_number"] == data["phone_number"] + def test_authkit_oauth_resource_round_trip(self): + data = load_fixture("authkit_oauth_resource.json") + instance = AuthkitOAuthResource.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = AuthkitOAuthResource.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_authkit_oauth_resource_minimal_payload(self): + data = { + "object": "authkit_oauth_resource", + "id": "authkit_oauth_resource_01EHZNVPK3SFK441A1RGBFSHRT", + "uri": "https://api.example.com", + "default": False, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = AuthkitOAuthResource.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["uri"] == data["uri"] + assert serialized["default"] == data["default"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + def test_redirect_uri_round_trip(self): data = load_fixture("redirect_uri.json") instance = RedirectUri.from_dict(data) From 21735d2185dfd37e5873b079d92ad8f16a54719f Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Mon, 14 Sep 2026 14:43:23 -0400 Subject: [PATCH 3/9] fix: reject empty and dot path segments before building request URLs (#728) --- src/workos/_base_client.py | 24 +++++++++++- tests/test_generated_client.py | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/workos/_base_client.py b/src/workos/_base_client.py index 198c29d68..bb1f17a3f 100644 --- a/src/workos/_base_client.py +++ b/src/workos/_base_client.py @@ -42,6 +42,10 @@ RETRY_MULTIPLIER = 2 +# Segments that URL normalization treats structurally; never valid identifiers. +_INVALID_PATH_SEGMENTS = frozenset({"", ".", ".."}) + + class _BaseWorkOSClient: """Shared WorkOS client implementation.""" @@ -145,7 +149,16 @@ def _encode_path(path: Sequence[str]) -> str: Callers pass each path component as a separate element (e.g. ``("organizations", organization_id)``). Each element is URL-encoded with ``safe=""`` so a caller-supplied id containing ``/``, ``?``, - ``#``, ``%``, or ``..`` cannot escape its intended segment — this is + ``#``, or ``%`` cannot escape its intended segment. + + Percent-encoding alone does not cover dot segments: ``.`` is an + unreserved character that :func:`urllib.parse.quote` leaves as-is, and + httpx applies RFC 3986 dot-segment removal when it builds the request + URL, so a bare ``.`` or ``..`` segment would collapse the path onto the + parent resource (for example, turning a DELETE of one connected account + into a DELETE of the whole user). Empty, ``.`` and ``..`` segments are + never valid WorkOS identifiers, so they are rejected with + ``ValueError`` before any request is made. Together these checks are the structural protection against forged cross-resource API requests under the application's API key. @@ -157,7 +170,14 @@ def _encode_path(path: Sequence[str]) -> str: raise TypeError( "path must be a sequence of segments (e.g. a tuple), not a str" ) - return "/".join(quote(str(seg), safe="") for seg in path) + segments = [str(seg) for seg in path] + for seg in segments: + if seg in _INVALID_PATH_SEGMENTS: + raise ValueError( + f"invalid URL path segment {seg!r}: path segments must be " + "non-empty and cannot be '.' or '..'" + ) + return "/".join(quote(seg, safe="") for seg in segments) def _resolve_timeout(self, request_options: Optional[RequestOptions]) -> float: timeout = self._request_timeout diff --git a/tests/test_generated_client.py b/tests/test_generated_client.py index 3c059fcd6..2847631fd 100644 --- a/tests/test_generated_client.py +++ b/tests/test_generated_client.py @@ -742,3 +742,72 @@ async def _sleep(_: float) -> None: with pytest.raises(generated_client_module.WorkOSConnectionError): await client.request("GET", ("test",)) await client.close() + + +class TestEncodePath: + """Path segments are encoded so caller-supplied ids cannot retarget a request.""" + + @pytest.mark.parametrize("segment", ["", ".", ".."]) + def test_rejects_empty_and_dot_segments(self, segment): + with pytest.raises(ValueError, match="invalid URL path segment"): + WorkOSClient._encode_path( + ("user_management", "users", "user_01", "connected_accounts", segment) + ) + + @pytest.mark.parametrize("segment", ["", ".", ".."]) + def test_async_client_rejects_empty_and_dot_segments(self, segment): + with pytest.raises(ValueError, match="invalid URL path segment"): + AsyncWorkOSClient._encode_path(("organizations", segment)) + + def test_rejects_bare_string_path(self): + with pytest.raises(TypeError): + WorkOSClient._encode_path("organizations/org_01") # type: ignore[arg-type] + + @pytest.mark.parametrize( + ("segment", "expected"), + [ + ("org_01", "org_01"), + ("a/b", "a%2Fb"), + ("a?b", "a%3Fb"), + ("a#b", "a%23b"), + ("a%b", "a%25b"), + ("%2e%2e", "%252e%252e"), + ("evil/../..", "evil%2F..%2F.."), + ("...", "..."), + (".hidden", ".hidden"), + ], + ) + def test_structural_characters_stay_inside_their_segment(self, segment, expected): + assert ( + WorkOSClient._encode_path(("organizations", segment)) + == f"organizations/{expected}" + ) + + def test_dot_segment_is_rejected_before_any_request(self, httpx_mock): + client = WorkOSClient(api_key="sk_test_123", client_id="client_test") + try: + with pytest.raises(ValueError, match="invalid URL path segment"): + client.pipes.delete_user_connected_account("user_01ABC", "..") + finally: + client.close() + assert httpx_mock.get_requests() == [] + + def test_build_url_rejects_dot_segments(self): + client = WorkOSClient(api_key="sk_test_123", client_id="client_test") + try: + with pytest.raises(ValueError, match="invalid URL path segment"): + client.build_url(("sso", ".."), {"client_id": "client_test"}) + finally: + client.close() + + +@pytest.mark.asyncio +class TestEncodePathAsync: + async def test_dot_segment_is_rejected_before_any_request(self, httpx_mock): + client = AsyncWorkOSClient(api_key="sk_test_123", client_id="client_test") + try: + with pytest.raises(ValueError, match="invalid URL path segment"): + await client.pipes.delete_user_connected_account("user_01ABC", "..") + finally: + await client.close() + assert httpx_mock.get_requests() == [] From 1948b82d6a543e2be8bc846e010cccbd1a5a5a22 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:50:01 -0400 Subject: [PATCH 4/9] chore(deps): update actions/deploy-pages action to v5.0.1 (#729) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 994b6de1b..6ad87e683 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -43,5 +43,5 @@ jobs: url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - - uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 + - uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 id: deployment From 7d3a7f10f0b72931e7839e0ee5462f6ae53ed7bc Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2026 19:30:52 +0000 Subject: [PATCH 5/9] fix(generated): Changes to multi_factor_auth (#732) Co-authored-by: workos-sdk-automation[bot] <255426317+workos-sdk-automation[bot]@users.noreply.github.com> --- ...49c30cb87778129af801fadc33546518853d045.md | 40 ++ .last-synced-sha | 2 +- .oagen-manifest.json | 51 ++ src/workos/_client.py | 14 + src/workos/agents/_resource.py | 36 +- src/workos/api_keys/models/api_key.py | 3 +- src/workos/authorization/_resource.py | 148 ++--- src/workos/common/__init__.py | 36 ++ src/workos/common/models/__init__.py | 46 ++ ...ent_registration_credential_issued_data.py | 3 +- .../common/models/api_key_created_data.py | 3 +- .../common/models/api_key_updated_data.py | 3 +- src/workos/common/models/connected_account.py | 23 + .../connected_account_connection_role.py | 27 + .../common/models/connected_account_input.py | 63 ++ .../models/create_webhook_endpoint_events.py | 14 + ...ntegration_installation_connection_role.py | 8 + ..._authorize_url_request_connection_owner.py | 10 + .../models/data_integrations_list_response.py | 43 ++ .../data_integrations_list_response_data.py | 146 +++++ ...ns_list_response_data_connected_account.py | 162 +++++ ..._data_connected_account_connection_role.py | 10 + ...ons_list_response_data_connection_owner.py | 10 + .../common/models/pipe_connected_account.py | 45 ++ .../pipe_connected_account_auth_method.py | 8 + .../pipe_connected_account_connection_role.py | 8 + .../pipes_account_connection_add_failed.py | 61 ++ ...ipes_account_connection_add_failed_data.py | 91 +++ .../pipes_account_connection_connected.py | 57 ++ ...es_account_connection_connection_failed.py | 61 ++ ...count_connection_connection_failed_data.py | 11 + .../pipes_account_connection_disconnected.py | 57 ++ ...count_connection_reauthorization_needed.py | 61 ++ ...onnected_account_connection_failed_data.py | 89 +-- ...rce_export_completed_data_resource_type.py | 9 +- src/workos/connect/_resource.py | 12 +- src/workos/directory_sync/_resource.py | 24 +- src/workos/events/_resource.py | 12 +- src/workos/events/models/event_schema.py | 25 + src/workos/feature_flags/_resource.py | 8 +- src/workos/groups/_resource.py | 24 +- src/workos/multi_factor_auth/_resource.py | 8 +- .../models/challenge_authentication_factor.py | 2 +- .../organization_membership/_resource.py | 24 +- src/workos/organizations/_resource.py | 24 +- .../organizations_data_providers/__init__.py | 7 + .../organizations_data_providers/_resource.py | 584 ++++++++++++++++++ .../models/__init__.py | 14 + src/workos/pipes/_resource.py | 53 +- src/workos/pipes/models/__init__.py | 14 +- .../pipes/models/create_data_integration.py | 4 +- .../data_integration_access_token_response.py | 4 +- .../data_integration_credentials_response.py | 4 +- .../models/data_integration_installation.py | 28 + ..._data_integration_authorize_url_request.py | 23 +- src/workos/radar/_resource.py | 8 +- ...standalone_update_radar_attempt_request.py | 4 +- src/workos/sso/_resource.py | 4 +- .../organizations_data_providers/__init__.py | 3 + src/workos/user_management/_resource.py | 104 ++-- .../models/create_authkit_oauth_resource.py | 2 +- src/workos/webhooks/_resource.py | 12 +- tests/fixtures/connected_account.json | 3 + .../data_integration_installation.json | 3 + ...ata_integration_authorize_url_request.json | 1 + .../data_integrations_list_response.json | 7 + .../data_integrations_list_response_data.json | 7 + ..._list_response_data_connected_account.json | 3 + tests/fixtures/pipe_connected_account.json | 5 + .../pipes_account_connection_add_failed.json | 39 ++ ...es_account_connection_add_failed_data.json | 13 + .../pipes_account_connection_connected.json | 46 ++ ..._account_connection_connection_failed.json | 39 ++ ...unt_connection_connection_failed_data.json | 13 + ...pipes_account_connection_disconnected.json | 46 ++ ...unt_connection_reauthorization_needed.json | 46 ++ .../pipes_connected_account_connected.json | 5 + ...s_connected_account_connection_failed.json | 1 + ...nected_account_connection_failed_data.json | 1 + .../pipes_connected_account_disconnected.json | 5 + ...nected_account_reauthorization_needed.json | 5 + tests/test_common_models_round_trip.py | 533 +++++++++++++++- tests/test_organizations_data_providers.py | 501 +++++++++++++++ ...ations_data_providers_models_round_trip.py | 441 +++++++++++++ tests/test_pipes.py | 2 +- tests/test_pipes_models_round_trip.py | 428 ++----------- 86 files changed, 3948 insertions(+), 739 deletions(-) create mode 100644 .changelog-pending/2026-09-16T19-24-23-949c30cb87778129af801fadc33546518853d045.md create mode 100644 src/workos/common/models/connected_account_connection_role.py create mode 100644 src/workos/common/models/connected_account_input.py create mode 100644 src/workos/common/models/data_integration_installation_connection_role.py create mode 100644 src/workos/common/models/data_integrations_get_data_integration_authorize_url_request_connection_owner.py create mode 100644 src/workos/common/models/data_integrations_list_response.py create mode 100644 src/workos/common/models/data_integrations_list_response_data.py create mode 100644 src/workos/common/models/data_integrations_list_response_data_connected_account.py create mode 100644 src/workos/common/models/data_integrations_list_response_data_connected_account_connection_role.py create mode 100644 src/workos/common/models/data_integrations_list_response_data_connection_owner.py create mode 100644 src/workos/common/models/pipe_connected_account_auth_method.py create mode 100644 src/workos/common/models/pipe_connected_account_connection_role.py create mode 100644 src/workos/common/models/pipes_account_connection_add_failed.py create mode 100644 src/workos/common/models/pipes_account_connection_add_failed_data.py create mode 100644 src/workos/common/models/pipes_account_connection_connected.py create mode 100644 src/workos/common/models/pipes_account_connection_connection_failed.py create mode 100644 src/workos/common/models/pipes_account_connection_connection_failed_data.py create mode 100644 src/workos/common/models/pipes_account_connection_disconnected.py create mode 100644 src/workos/common/models/pipes_account_connection_reauthorization_needed.py create mode 100644 src/workos/organizations_data_providers/__init__.py create mode 100644 src/workos/organizations_data_providers/_resource.py create mode 100644 src/workos/organizations_data_providers/models/__init__.py create mode 100644 src/workos/types/organizations_data_providers/__init__.py create mode 100644 tests/fixtures/pipes_account_connection_add_failed.json create mode 100644 tests/fixtures/pipes_account_connection_add_failed_data.json create mode 100644 tests/fixtures/pipes_account_connection_connected.json create mode 100644 tests/fixtures/pipes_account_connection_connection_failed.json create mode 100644 tests/fixtures/pipes_account_connection_connection_failed_data.json create mode 100644 tests/fixtures/pipes_account_connection_disconnected.json create mode 100644 tests/fixtures/pipes_account_connection_reauthorization_needed.json create mode 100644 tests/test_organizations_data_providers.py create mode 100644 tests/test_organizations_data_providers_models_round_trip.py diff --git a/.changelog-pending/2026-09-16T19-24-23-949c30cb87778129af801fadc33546518853d045.md b/.changelog-pending/2026-09-16T19-24-23-949c30cb87778129af801fadc33546518853d045.md new file mode 100644 index 000000000..744a55f68 --- /dev/null +++ b/.changelog-pending/2026-09-16T19-24-23-949c30cb87778129af801fadc33546518853d045.md @@ -0,0 +1,40 @@ +* [#732](https://github.com/workos/workos-python/pull/732) fix(generated): regenerate from spec (1 change) + + **Features** + * **[multi_factor_auth](https://workos.com/docs/reference/authkit/mfa)**: + * Changed errors for endpoint `POST /auth/factors/{id}/challenge` + * **[pipes](https://workos.com/docs/reference/pipes)**: + * Added `connection_owner` to `DataIntegrationsListResponseData` + * Added enum `DataIntegrationsListResponseDataConnectionOwner` + * Added `connection_role` to `DataIntegrationsListResponseDataConnectedAccount` + * Added `account_identifier` to `DataIntegrationsListResponseDataConnectedAccount` + * Added `account_display_name` to `DataIntegrationsListResponseDataConnectedAccount` + * Added enum `DataIntegrationsListResponseDataConnectedAccountConnectionRole` + * Added `connection_role` to `DataIntegrationInstallation` + * Added `account_identifier` to `DataIntegrationInstallation` + * Added `account_display_name` to `DataIntegrationInstallation` + * Added enum `DataIntegrationInstallationConnectionRole` + * Added `connection_owner` to `DataIntegrationsGetDataIntegrationAuthorizeUrlRequest` + * Added enum `DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner` + * **[connect](https://workos.com/docs/reference/workos-connect/standalone)**: + * Added `connection_role` to `ConnectedAccount` + * Added `account_identifier` to `ConnectedAccount` + * Added `account_display_name` to `ConnectedAccount` + * Added enum `ConnectedAccountConnectionRole` + * **[organizations](https://workos.com/docs/reference/organization)**: + * Added service `OrganizationsDataProviders` + * **[webhooks](https://workos.com/docs/reference/webhooks)**: + * Added `pipes.account_connection.add_failed` to `CreateWebhookEndpointEvents` + * Added `pipes.account_connection.connected` to `CreateWebhookEndpointEvents` + * Added `pipes.account_connection.connection_failed` to `CreateWebhookEndpointEvents` + * Added `pipes.account_connection.disconnected` to `CreateWebhookEndpointEvents` + * Added `pipes.account_connection.reauthorization_needed` to `CreateWebhookEndpointEvents` + * Added `pipes.account_connection.add_failed` to `UpdateWebhookEndpointEvents` + * Added `pipes.account_connection.connected` to `UpdateWebhookEndpointEvents` + * Added `pipes.account_connection.connection_failed` to `UpdateWebhookEndpointEvents` + * Added `pipes.account_connection.disconnected` to `UpdateWebhookEndpointEvents` + * Added `pipes.account_connection.reauthorization_needed` to `UpdateWebhookEndpointEvents` + + **Fixes** + * **[multi_factor_auth](https://workos.com/docs/reference/authkit/mfa)**: + * Changed errors for endpoint `POST /auth/factors/{id}/challenge` diff --git a/.last-synced-sha b/.last-synced-sha index 1c2b758d6..b7efc9db8 100644 --- a/.last-synced-sha +++ b/.last-synced-sha @@ -1 +1 @@ -d3fa61120f8b9e73972b9dc5f4b5ea2bc0526636 +13ba15b32754d2d635b7799ab779867df2b611d6 diff --git a/.oagen-manifest.json b/.oagen-manifest.json index 587c9f92b..056fa1e7f 100644 --- a/.oagen-manifest.json +++ b/.oagen-manifest.json @@ -247,6 +247,8 @@ "src/workos/common/models/connect_application_oauth_redirect_uris.py", "src/workos/common/models/connected_account.py", "src/workos/common/models/connected_account_auth_method.py", + "src/workos/common/models/connected_account_connection_role.py", + "src/workos/common/models/connected_account_input.py", "src/workos/common/models/connected_account_input_state.py", "src/workos/common/models/connected_account_state.py", "src/workos/common/models/connection_activated.py", @@ -294,12 +296,19 @@ "src/workos/common/models/data_integration_credentials_input_type.py", "src/workos/common/models/data_integration_credentials_response_error.py", "src/workos/common/models/data_integration_custom_provider_authenticate_via.py", + "src/workos/common/models/data_integration_installation_connection_role.py", "src/workos/common/models/data_integration_ownership.py", "src/workos/common/models/data_integration_state.py", + "src/workos/common/models/data_integrations_get_data_integration_authorize_url_request_connection_owner.py", "src/workos/common/models/data_integrations_get_user_token_request_connection_owner.py", + "src/workos/common/models/data_integrations_list_response.py", + "src/workos/common/models/data_integrations_list_response_data.py", "src/workos/common/models/data_integrations_list_response_data_auth_methods.py", + "src/workos/common/models/data_integrations_list_response_data_connected_account.py", "src/workos/common/models/data_integrations_list_response_data_connected_account_auth_method.py", + "src/workos/common/models/data_integrations_list_response_data_connected_account_connection_role.py", "src/workos/common/models/data_integrations_list_response_data_connected_account_state.py", + "src/workos/common/models/data_integrations_list_response_data_connection_owner.py", "src/workos/common/models/data_integrations_list_response_data_ownership.py", "src/workos/common/models/data_integrations_upsert_api_key_request_connection_owner.py", "src/workos/common/models/data_integrations_upsert_client_credentials_request_connection_owner.py", @@ -483,7 +492,16 @@ "src/workos/common/models/permission_updated.py", "src/workos/common/models/permission_updated_data.py", "src/workos/common/models/pipe_connected_account.py", + "src/workos/common/models/pipe_connected_account_auth_method.py", + "src/workos/common/models/pipe_connected_account_connection_role.py", "src/workos/common/models/pipe_connected_account_state.py", + "src/workos/common/models/pipes_account_connection_add_failed.py", + "src/workos/common/models/pipes_account_connection_add_failed_data.py", + "src/workos/common/models/pipes_account_connection_connected.py", + "src/workos/common/models/pipes_account_connection_connection_failed.py", + "src/workos/common/models/pipes_account_connection_connection_failed_data.py", + "src/workos/common/models/pipes_account_connection_disconnected.py", + "src/workos/common/models/pipes_account_connection_reauthorization_needed.py", "src/workos/common/models/pipes_connected_account_connected.py", "src/workos/common/models/pipes_connected_account_connection_failed.py", "src/workos/common/models/pipes_connected_account_connection_failed_data.py", @@ -672,6 +690,9 @@ "src/workos/organizations/models/organization_input.py", "src/workos/organizations/models/update_audit_logs_retention.py", "src/workos/organizations/models/update_organization.py", + "src/workos/organizations_data_providers/__init__.py", + "src/workos/organizations_data_providers/_resource.py", + "src/workos/organizations_data_providers/models/__init__.py", "src/workos/pipes/__init__.py", "src/workos/pipes/_resource.py", "src/workos/pipes/models/__init__.py", @@ -771,6 +792,7 @@ "src/workos/types/organization_domains/__init__.py", "src/workos/types/organization_membership/__init__.py", "src/workos/types/organizations/__init__.py", + "src/workos/types/organizations_data_providers/__init__.py", "src/workos/types/pipes/__init__.py", "src/workos/types/pipes_provider/__init__.py", "src/workos/types/platform_teams/__init__.py", @@ -1366,6 +1388,13 @@ "tests/fixtures/permission_updated.json", "tests/fixtures/permission_updated_data.json", "tests/fixtures/pipe_connected_account.json", + "tests/fixtures/pipes_account_connection_add_failed.json", + "tests/fixtures/pipes_account_connection_add_failed_data.json", + "tests/fixtures/pipes_account_connection_connected.json", + "tests/fixtures/pipes_account_connection_connection_failed.json", + "tests/fixtures/pipes_account_connection_connection_failed_data.json", + "tests/fixtures/pipes_account_connection_disconnected.json", + "tests/fixtures/pipes_account_connection_reauthorization_needed.json", "tests/fixtures/pipes_connected_account_connected.json", "tests/fixtures/pipes_connected_account_connection_failed.json", "tests/fixtures/pipes_connected_account_connection_failed_data.json", @@ -1544,6 +1573,8 @@ "tests/test_organization_membership.py", "tests/test_organization_membership_models_round_trip.py", "tests/test_organizations.py", + "tests/test_organizations_data_providers.py", + "tests/test_organizations_data_providers_models_round_trip.py", "tests/test_organizations_models_round_trip.py", "tests/test_pipes.py", "tests/test_pipes_models_round_trip.py", @@ -2588,6 +2619,26 @@ "DELETE /user_management/authkit_oauth_resources/{id}": { "sdkMethod": "delete_authkit_oauth_resource", "service": "user_management" + }, + "GET /organizations/{organization_id}/connected_accounts/{slug}": { + "sdkMethod": "get_organization_connected_account", + "service": "organizations_data_providers" + }, + "POST /organizations/{organization_id}/connected_accounts/{slug}": { + "sdkMethod": "create_organization_connected_account", + "service": "organizations_data_providers" + }, + "PUT /organizations/{organization_id}/connected_accounts/{slug}": { + "sdkMethod": "update_organization_connected_account", + "service": "organizations_data_providers" + }, + "DELETE /organizations/{organization_id}/connected_accounts/{slug}": { + "sdkMethod": "delete_organization_connected_account", + "service": "organizations_data_providers" + }, + "GET /organizations/{organization_id}/data_providers": { + "sdkMethod": "list_organization_data_providers", + "service": "organizations_data_providers" } } } diff --git a/src/workos/_client.py b/src/workos/_client.py index 9776dd230..035d16f67 100644 --- a/src/workos/_client.py +++ b/src/workos/_client.py @@ -32,6 +32,10 @@ OrganizationMembershipService, ) from .organizations._resource import AsyncOrganizations, Organizations +from .organizations_data_providers._resource import ( + AsyncOrganizationsDataProviders, + OrganizationsDataProviders, +) from .passwordless import AsyncPasswordless, Passwordless from .pipes._resource import AsyncPipes, Pipes from .pipes_provider._resource import AsyncPipesProvider, PipesProvider @@ -123,6 +127,11 @@ def groups(self) -> Groups: """Groups API resources.""" return Groups(self) + @functools.cached_property + def organizations_data_providers(self) -> OrganizationsDataProviders: + """Organizations Data Providers API resources.""" + return OrganizationsDataProviders(self) + @functools.cached_property def platform_teams(self) -> PlatformTeams: """Platform Teams API resources.""" @@ -271,6 +280,11 @@ def groups(self) -> AsyncGroups: """Groups API resources.""" return AsyncGroups(self) + @functools.cached_property + def organizations_data_providers(self) -> AsyncOrganizationsDataProviders: + """Organizations Data Providers API resources.""" + return AsyncOrganizationsDataProviders(self) + @functools.cached_property def platform_teams(self) -> AsyncPlatformTeams: """Platform Teams API resources.""" diff --git a/src/workos/agents/_resource.py b/src/workos/agents/_resource.py index 34ade28aa..a9325f5fa 100644 --- a/src/workos/agents/_resource.py +++ b/src/workos/agents/_resource.py @@ -55,9 +55,9 @@ def list_blueprints( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -498,9 +498,9 @@ def list_instances( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. organization_id: Only return instances acting within this organization. agent_blueprint_id: Only return instances minted from this blueprint. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -607,9 +607,9 @@ def list_sessions( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. organization_id: Only return sessions of instances acting within this organization. agent_blueprint_id: Only return sessions of instances minted from this blueprint. agent_instance_id: Only return sessions belonging to this agent instance. @@ -727,9 +727,9 @@ async def list_blueprints( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -1170,9 +1170,9 @@ async def list_instances( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. organization_id: Only return instances acting within this organization. agent_blueprint_id: Only return instances minted from this blueprint. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -1279,9 +1279,9 @@ async def list_sessions( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. organization_id: Only return sessions of instances acting within this organization. agent_blueprint_id: Only return sessions of instances minted from this blueprint. agent_instance_id: Only return sessions belonging to this agent instance. diff --git a/src/workos/api_keys/models/api_key.py b/src/workos/api_keys/models/api_key.py index 05228dbbc..4b941613d 100644 --- a/src/workos/api_keys/models/api_key.py +++ b/src/workos/api_keys/models/api_key.py @@ -51,7 +51,8 @@ def from_dict(cls, data: dict[str, Any]) -> ApiKey: _owner_cls = _owner_disc_map.get(_owner_disc) if _owner_cls is None: raise ValueError( - f"Unknown discriminator 'type' for ApiKey.owner: {_owner_disc!r}. " + "Unknown discriminator 'type' for ApiKey.owner: " + f"{_owner_disc!r}. " f"Expected one of {sorted(_owner_disc_map)}." ) return cls( diff --git a/src/workos/authorization/_resource.py b/src/workos/authorization/_resource.py index d654d4937..a1b6f72cb 100644 --- a/src/workos/authorization/_resource.py +++ b/src/workos/authorization/_resource.py @@ -99,9 +99,9 @@ def list_group_role_assignments( Args: group_id: The ID of the group. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -407,16 +407,16 @@ def list_resources_for_membership( ) -> SyncPage[AuthorizationResource]: """List resources for organization membership - Returns all child resources of a parent resource where the organization membership has a specific permission. This is useful for resource discovery—answering "What projects can this user access in this workspace?" + Returns all child resources of a parent resource where the organization membership has a specific permission. This is useful for resource discovery—answering \"What projects can this user access in this workspace?\" You must provide either `parent_resource_id` or both `parent_resource_external_id` and `parent_resource_type_slug` to identify the parent resource. Args: organization_membership_id: The ID of the organization membership. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. permission_slug: The permission slug to filter by. Only child resources where the organization membership has this permission are returned. parent_resource: Identifies the parent resource. One of: ParentResourceById, ParentResourceByExternalId. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -485,9 +485,9 @@ def list_effective_permissions( organization_membership_id: The ID of the organization membership. resource_id: The ID of the authorization resource. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -547,9 +547,9 @@ def list_effective_permissions_by_external_id( resource_type_slug: The slug of the resource type. external_id: An identifier you provide to reference the resource in your system. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -609,9 +609,9 @@ def list_role_assignments( Args: organization_membership_id: The ID of the organization membership. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. resource_id: Filter assignments by the ID of the resource. resource_external_id: Filter assignments by the external ID of the resource. resource_type_slug: Filter assignments by the slug of the resource type. @@ -1313,18 +1313,18 @@ def list_memberships_for_resource_by_external_id( ) -> SyncPage[UserOrganizationMembershipBaseListData]: """List memberships for a resource by external ID - Returns all organization memberships that have a specific permission on a resource, using the resource's external ID. This is useful for answering "Who can access this resource?" when you only have the external ID. + Returns all organization memberships that have a specific permission on a resource, using the resource's external ID. This is useful for answering \"Who can access this resource?\" when you only have the external ID. Args: organization_id: The ID of the organization that owns the resource. resource_type_slug: The slug of the resource type this resource belongs to. external_id: An identifier you provide to reference the resource in your system. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. permission_slug: The permission slug to filter by. Only users with this permission on the resource are returned. - assignment: Filter by assignment type. Use "direct" for direct assignments only, or "indirect" to include inherited assignments. + assignment: Filter by assignment type. Use \"direct\" for direct assignments only, or \"indirect\" to include inherited assignments. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -1391,9 +1391,9 @@ def list_role_assignments_for_resource_by_external_id( resource_type_slug: The slug of the resource type. external_id: An identifier you provide to reference the resource in your system. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. role_slug: Filter assignments by the slug of the role. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -1453,9 +1453,9 @@ def list_resources( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. organization_id: Filter resources by organization ID. resource_type_slug: Filter resources by resource type slug. resource_external_id: Filter resources by external ID. @@ -1708,14 +1708,14 @@ def list_memberships_for_resource( ) -> SyncPage[UserOrganizationMembershipBaseListData]: """List organization memberships for resource - Returns all organization memberships that have a specific permission on a resource instance. This is useful for answering "Who can access this resource?". + Returns all organization memberships that have a specific permission on a resource instance. This is useful for answering \"Who can access this resource?\". Args: resource_id: The ID of the authorization resource. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. permission_slug: The permission slug to filter by. Only users with this permission on the resource are returned. assignment: Filter by assignment type. Use `direct` for direct assignments only, or `indirect` to include inherited assignments. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -1777,9 +1777,9 @@ def list_role_assignments_for_resource( Args: resource_id: The ID of the authorization resource. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. role_slug: Filter assignments by the slug of the role. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -2060,9 +2060,9 @@ def list_permissions( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -2270,9 +2270,9 @@ async def list_group_role_assignments( Args: group_id: The ID of the group. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -2578,16 +2578,16 @@ async def list_resources_for_membership( ) -> AsyncPage[AuthorizationResource]: """List resources for organization membership - Returns all child resources of a parent resource where the organization membership has a specific permission. This is useful for resource discovery—answering "What projects can this user access in this workspace?" + Returns all child resources of a parent resource where the organization membership has a specific permission. This is useful for resource discovery—answering \"What projects can this user access in this workspace?\" You must provide either `parent_resource_id` or both `parent_resource_external_id` and `parent_resource_type_slug` to identify the parent resource. Args: organization_membership_id: The ID of the organization membership. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. permission_slug: The permission slug to filter by. Only child resources where the organization membership has this permission are returned. parent_resource: Identifies the parent resource. One of: ParentResourceById, ParentResourceByExternalId. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -2656,9 +2656,9 @@ async def list_effective_permissions( organization_membership_id: The ID of the organization membership. resource_id: The ID of the authorization resource. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -2718,9 +2718,9 @@ async def list_effective_permissions_by_external_id( resource_type_slug: The slug of the resource type. external_id: An identifier you provide to reference the resource in your system. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -2780,9 +2780,9 @@ async def list_role_assignments( Args: organization_membership_id: The ID of the organization membership. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. resource_id: Filter assignments by the ID of the resource. resource_external_id: Filter assignments by the external ID of the resource. resource_type_slug: Filter assignments by the slug of the resource type. @@ -3484,18 +3484,18 @@ async def list_memberships_for_resource_by_external_id( ) -> AsyncPage[UserOrganizationMembershipBaseListData]: """List memberships for a resource by external ID - Returns all organization memberships that have a specific permission on a resource, using the resource's external ID. This is useful for answering "Who can access this resource?" when you only have the external ID. + Returns all organization memberships that have a specific permission on a resource, using the resource's external ID. This is useful for answering \"Who can access this resource?\" when you only have the external ID. Args: organization_id: The ID of the organization that owns the resource. resource_type_slug: The slug of the resource type this resource belongs to. external_id: An identifier you provide to reference the resource in your system. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. permission_slug: The permission slug to filter by. Only users with this permission on the resource are returned. - assignment: Filter by assignment type. Use "direct" for direct assignments only, or "indirect" to include inherited assignments. + assignment: Filter by assignment type. Use \"direct\" for direct assignments only, or \"indirect\" to include inherited assignments. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -3562,9 +3562,9 @@ async def list_role_assignments_for_resource_by_external_id( resource_type_slug: The slug of the resource type. external_id: An identifier you provide to reference the resource in your system. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. role_slug: Filter assignments by the slug of the role. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -3624,9 +3624,9 @@ async def list_resources( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. organization_id: Filter resources by organization ID. resource_type_slug: Filter resources by resource type slug. resource_external_id: Filter resources by external ID. @@ -3879,14 +3879,14 @@ async def list_memberships_for_resource( ) -> AsyncPage[UserOrganizationMembershipBaseListData]: """List organization memberships for resource - Returns all organization memberships that have a specific permission on a resource instance. This is useful for answering "Who can access this resource?". + Returns all organization memberships that have a specific permission on a resource instance. This is useful for answering \"Who can access this resource?\". Args: resource_id: The ID of the authorization resource. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. permission_slug: The permission slug to filter by. Only users with this permission on the resource are returned. assignment: Filter by assignment type. Use `direct` for direct assignments only, or `indirect` to include inherited assignments. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -3948,9 +3948,9 @@ async def list_role_assignments_for_resource( Args: resource_id: The ID of the authorization resource. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. role_slug: Filter assignments by the slug of the role. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -4231,9 +4231,9 @@ async def list_permissions( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: diff --git a/src/workos/common/__init__.py b/src/workos/common/__init__.py index 931b156b0..3d04b3251 100644 --- a/src/workos/common/__init__.py +++ b/src/workos/common/__init__.py @@ -221,6 +221,8 @@ from .models import ConnectApplicationVariant as ConnectApplicationVariant from .models import ConnectedAccount as ConnectedAccount from .models import ConnectedAccountAuthMethod as ConnectedAccountAuthMethod +from .models import ConnectedAccountConnectionRole as ConnectedAccountConnectionRole +from .models import ConnectedAccountInput as ConnectedAccountInput from .models import ConnectedAccountInputState as ConnectedAccountInputState from .models import ConnectedAccountState as ConnectedAccountState from .models import ConnectionActivated as ConnectionActivated @@ -288,19 +290,36 @@ from .models import ( DataIntegrationCustomProviderAuthenticateVia as DataIntegrationCustomProviderAuthenticateVia, ) +from .models import ( + DataIntegrationInstallationConnectionRole as DataIntegrationInstallationConnectionRole, +) from .models import DataIntegrationOwnership as DataIntegrationOwnership +from .models import ( + DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner as DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner, +) from .models import ( DataIntegrationsGetUserTokenRequestConnectionOwner as DataIntegrationsGetUserTokenRequestConnectionOwner, ) +from .models import DataIntegrationsListResponse as DataIntegrationsListResponse +from .models import DataIntegrationsListResponseData as DataIntegrationsListResponseData from .models import ( DataIntegrationsListResponseDataAuthMethods as DataIntegrationsListResponseDataAuthMethods, ) +from .models import ( + DataIntegrationsListResponseDataConnectedAccount as DataIntegrationsListResponseDataConnectedAccount, +) from .models import ( DataIntegrationsListResponseDataConnectedAccountAuthMethod as DataIntegrationsListResponseDataConnectedAccountAuthMethod, ) +from .models import ( + DataIntegrationsListResponseDataConnectedAccountConnectionRole as DataIntegrationsListResponseDataConnectedAccountConnectionRole, +) from .models import ( DataIntegrationsListResponseDataConnectedAccountState as DataIntegrationsListResponseDataConnectedAccountState, ) +from .models import ( + DataIntegrationsListResponseDataConnectionOwner as DataIntegrationsListResponseDataConnectionOwner, +) from .models import ( DataIntegrationsListResponseDataOwnership as DataIntegrationsListResponseDataOwnership, ) @@ -498,6 +517,23 @@ from .models import PermissionUpdated as PermissionUpdated from .models import PermissionUpdatedData as PermissionUpdatedData from .models import PipeConnectedAccount as PipeConnectedAccount +from .models import PipesAccountConnectionAddFailed as PipesAccountConnectionAddFailed +from .models import ( + PipesAccountConnectionAddFailedData as PipesAccountConnectionAddFailedData, +) +from .models import PipesAccountConnectionConnected as PipesAccountConnectionConnected +from .models import ( + PipesAccountConnectionConnectionFailed as PipesAccountConnectionConnectionFailed, +) +from .models import ( + PipesAccountConnectionConnectionFailedData as PipesAccountConnectionConnectionFailedData, +) +from .models import ( + PipesAccountConnectionDisconnected as PipesAccountConnectionDisconnected, +) +from .models import ( + PipesAccountConnectionReauthorizationNeeded as PipesAccountConnectionReauthorizationNeeded, +) from .models import PipesConnectedAccountConnected as PipesConnectedAccountConnected from .models import ( PipesConnectedAccountConnectionFailed as PipesConnectedAccountConnectionFailed, diff --git a/src/workos/common/models/__init__.py b/src/workos/common/models/__init__.py index 5890f9ad8..7aa3f369f 100644 --- a/src/workos/common/models/__init__.py +++ b/src/workos/common/models/__init__.py @@ -366,6 +366,10 @@ from .connected_account_auth_method import ( ConnectedAccountAuthMethod as ConnectedAccountAuthMethod, ) +from .connected_account_connection_role import ( + ConnectedAccountConnectionRole as ConnectedAccountConnectionRole, +) +from .connected_account_input import ConnectedAccountInput as ConnectedAccountInput from .connected_account_input_state import ( ConnectedAccountInputState as ConnectedAccountInputState, ) @@ -471,22 +475,43 @@ from .data_integration_custom_provider_authenticate_via import ( DataIntegrationCustomProviderAuthenticateVia as DataIntegrationCustomProviderAuthenticateVia, ) +from .data_integration_installation_connection_role import ( + DataIntegrationInstallationConnectionRole as DataIntegrationInstallationConnectionRole, +) from .data_integration_ownership import ( DataIntegrationOwnership as DataIntegrationOwnership, ) from .data_integration_state import DataIntegrationState as DataIntegrationState +from .data_integrations_get_data_integration_authorize_url_request_connection_owner import ( + DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner as DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner, +) from .data_integrations_get_user_token_request_connection_owner import ( DataIntegrationsGetUserTokenRequestConnectionOwner as DataIntegrationsGetUserTokenRequestConnectionOwner, ) +from .data_integrations_list_response import ( + DataIntegrationsListResponse as DataIntegrationsListResponse, +) +from .data_integrations_list_response_data import ( + DataIntegrationsListResponseData as DataIntegrationsListResponseData, +) from .data_integrations_list_response_data_auth_methods import ( DataIntegrationsListResponseDataAuthMethods as DataIntegrationsListResponseDataAuthMethods, ) +from .data_integrations_list_response_data_connected_account import ( + DataIntegrationsListResponseDataConnectedAccount as DataIntegrationsListResponseDataConnectedAccount, +) from .data_integrations_list_response_data_connected_account_auth_method import ( DataIntegrationsListResponseDataConnectedAccountAuthMethod as DataIntegrationsListResponseDataConnectedAccountAuthMethod, ) +from .data_integrations_list_response_data_connected_account_connection_role import ( + DataIntegrationsListResponseDataConnectedAccountConnectionRole as DataIntegrationsListResponseDataConnectedAccountConnectionRole, +) from .data_integrations_list_response_data_connected_account_state import ( DataIntegrationsListResponseDataConnectedAccountState as DataIntegrationsListResponseDataConnectedAccountState, ) +from .data_integrations_list_response_data_connection_owner import ( + DataIntegrationsListResponseDataConnectionOwner as DataIntegrationsListResponseDataConnectionOwner, +) from .data_integrations_list_response_data_ownership import ( DataIntegrationsListResponseDataOwnership as DataIntegrationsListResponseDataOwnership, ) @@ -801,6 +826,27 @@ from .permission_updated_data import PermissionUpdatedData as PermissionUpdatedData from .pipe_connected_account import PipeConnectedAccount as PipeConnectedAccount from .pipe_connected_account_state import * +from .pipes_account_connection_add_failed import ( + PipesAccountConnectionAddFailed as PipesAccountConnectionAddFailed, +) +from .pipes_account_connection_add_failed_data import ( + PipesAccountConnectionAddFailedData as PipesAccountConnectionAddFailedData, +) +from .pipes_account_connection_connected import ( + PipesAccountConnectionConnected as PipesAccountConnectionConnected, +) +from .pipes_account_connection_connection_failed import ( + PipesAccountConnectionConnectionFailed as PipesAccountConnectionConnectionFailed, +) +from .pipes_account_connection_connection_failed_data import ( + PipesAccountConnectionConnectionFailedData as PipesAccountConnectionConnectionFailedData, +) +from .pipes_account_connection_disconnected import ( + PipesAccountConnectionDisconnected as PipesAccountConnectionDisconnected, +) +from .pipes_account_connection_reauthorization_needed import ( + PipesAccountConnectionReauthorizationNeeded as PipesAccountConnectionReauthorizationNeeded, +) from .pipes_connected_account_connected import ( PipesConnectedAccountConnected as PipesConnectedAccountConnected, ) diff --git a/src/workos/common/models/agent_registration_credential_issued_data.py b/src/workos/common/models/agent_registration_credential_issued_data.py index c6e225f36..61aa98916 100644 --- a/src/workos/common/models/agent_registration_credential_issued_data.py +++ b/src/workos/common/models/agent_registration_credential_issued_data.py @@ -49,7 +49,8 @@ def from_dict(cls, data: dict[str, Any]) -> AgentRegistrationCredentialIssuedDat _detail_cls = _detail_disc_map.get(_detail_disc) if _detail_cls is None: raise ValueError( - f"Unknown discriminator 'kind' for AgentRegistrationCredentialIssuedData.detail: {_detail_disc!r}. " + "Unknown discriminator 'kind' for AgentRegistrationCredentialIssuedData.detail: " + f"{_detail_disc!r}. " f"Expected one of {sorted(_detail_disc_map)}." ) return cls( diff --git a/src/workos/common/models/api_key_created_data.py b/src/workos/common/models/api_key_created_data.py index ce67304ca..ffb530793 100644 --- a/src/workos/common/models/api_key_created_data.py +++ b/src/workos/common/models/api_key_created_data.py @@ -51,7 +51,8 @@ def from_dict(cls, data: dict[str, Any]) -> ApiKeyCreatedData: _owner_cls = _owner_disc_map.get(_owner_disc) if _owner_cls is None: raise ValueError( - f"Unknown discriminator 'type' for ApiKeyCreatedData.owner: {_owner_disc!r}. " + "Unknown discriminator 'type' for ApiKeyCreatedData.owner: " + f"{_owner_disc!r}. " f"Expected one of {sorted(_owner_disc_map)}." ) return cls( diff --git a/src/workos/common/models/api_key_updated_data.py b/src/workos/common/models/api_key_updated_data.py index 22df787e9..c1042b2f5 100644 --- a/src/workos/common/models/api_key_updated_data.py +++ b/src/workos/common/models/api_key_updated_data.py @@ -54,7 +54,8 @@ def from_dict(cls, data: dict[str, Any]) -> ApiKeyUpdatedData: _owner_cls = _owner_disc_map.get(_owner_disc) if _owner_cls is None: raise ValueError( - f"Unknown discriminator 'type' for ApiKeyUpdatedData.owner: {_owner_disc!r}. " + "Unknown discriminator 'type' for ApiKeyUpdatedData.owner: " + f"{_owner_disc!r}. " f"Expected one of {sorted(_owner_disc_map)}." ) return cls( diff --git a/src/workos/common/models/connected_account.py b/src/workos/common/models/connected_account.py index 838d51591..2eb3a1830 100644 --- a/src/workos/common/models/connected_account.py +++ b/src/workos/common/models/connected_account.py @@ -9,6 +9,7 @@ from workos._types import _raise_deserialize_error from .connected_account_auth_method import ConnectedAccountAuthMethod +from .connected_account_connection_role import ConnectedAccountConnectionRole from .connected_account_state import ConnectedAccountState @@ -20,6 +21,12 @@ class ConnectedAccount: """Distinguishes the connected account object.""" id: str """The unique identifier of the connected account.""" + connection_role: ConnectedAccountConnectionRole + """Whether this row is the compatibility connection visible to undeclared clients or a standard peer for plural-aware clients. The role does not indicate preference or creation order.""" + account_identifier: str | None + """A best-effort identifier for the provider account this connection points at. It is correlation metadata, not the connection identifier or a selector.""" + account_display_name: str | None + """A mutable, non-unique display name for the provider account connection.""" user_id: str | None """The [User](https://workos.com/docs/reference/authkit/user) identifier associated with this connection.""" organization_id: str | None @@ -53,6 +60,9 @@ def from_dict(cls, data: dict[str, Any]) -> ConnectedAccount: return cls( object=data.get("object", "connected_account"), id=data["id"], + connection_role=ConnectedAccountConnectionRole(data["connection_role"]), + account_identifier=data["account_identifier"], + account_display_name=data["account_display_name"], user_id=data["user_id"], organization_id=data["organization_id"], scopes=data["scopes"], @@ -75,6 +85,19 @@ def to_dict(self) -> dict[str, Any]: result: dict[str, Any] = {} result["object"] = self.object result["id"] = self.id + result["connection_role"] = ( + self.connection_role.value + if isinstance(self.connection_role, Enum) + else self.connection_role + ) + if self.account_identifier is not None: + result["account_identifier"] = self.account_identifier + else: + result["account_identifier"] = None + if self.account_display_name is not None: + result["account_display_name"] = self.account_display_name + else: + result["account_display_name"] = None if self.user_id is not None: result["user_id"] = self.user_id else: diff --git a/src/workos/common/models/connected_account_connection_role.py b/src/workos/common/models/connected_account_connection_role.py new file mode 100644 index 000000000..6a33b4bba --- /dev/null +++ b/src/workos/common/models/connected_account_connection_role.py @@ -0,0 +1,27 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of connected account connection role values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Literal, TypeAlias + + +class ConnectedAccountConnectionRole(str, Enum): + """Known values for ConnectedAccountConnectionRole.""" + + COMPATIBILITY = "compatibility" + STANDARD = "standard" + + @classmethod + def _missing_(cls, value: object) -> ConnectedAccountConnectionRole | None: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +ConnectedAccountConnectionRoleLiteral: TypeAlias = Literal["compatibility", "standard"] diff --git a/src/workos/common/models/connected_account_input.py b/src/workos/common/models/connected_account_input.py new file mode 100644 index 000000000..fe664f5b7 --- /dev/null +++ b/src/workos/common/models/connected_account_input.py @@ -0,0 +1,63 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from enum import Enum +from typing import Any + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .connected_account_input_state import ConnectedAccountInputState + + +@dataclass(slots=True) +class ConnectedAccountInput: + """Connected Account Input model.""" + + access_token: str | None = None + """The OAuth access token for the connected account.""" + refresh_token: str | None = None + """The OAuth refresh token for the connected account.""" + expires_at: datetime | None = None + """The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire.""" + scopes: list[str] | None = None + """The OAuth scopes granted for this connection.""" + state: ConnectedAccountInputState | None = None + """Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> ConnectedAccountInput: + """Deserialize from a dictionary.""" + try: + return cls( + access_token=data.get("access_token"), + refresh_token=data.get("refresh_token"), + expires_at=_parse_datetime(_v_expires_at) + if (_v_expires_at := data.get("expires_at")) is not None + else None, + scopes=data.get("scopes"), + state=ConnectedAccountInputState(_v_state) + if (_v_state := data.get("state")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("ConnectedAccountInput", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + if self.access_token is not None: + result["access_token"] = self.access_token + if self.refresh_token is not None: + result["refresh_token"] = self.refresh_token + if self.expires_at is not None: + result["expires_at"] = _format_datetime(self.expires_at) + if self.scopes is not None: + result["scopes"] = self.scopes + if self.state is not None: + result["state"] = ( + self.state.value if isinstance(self.state, Enum) else self.state + ) + return result diff --git a/src/workos/common/models/create_webhook_endpoint_events.py b/src/workos/common/models/create_webhook_endpoint_events.py index 987fbc4e8..f288ba997 100644 --- a/src/workos/common/models/create_webhook_endpoint_events.py +++ b/src/workos/common/models/create_webhook_endpoint_events.py @@ -112,6 +112,15 @@ class CreateWebhookEndpointEvents(str, Enum): PERMISSION_CREATED = "permission.created" PERMISSION_DELETED = "permission.deleted" PERMISSION_UPDATED = "permission.updated" + PIPES_ACCOUNT_CONNECTION_ADD_FAILED = "pipes.account_connection.add_failed" + PIPES_ACCOUNT_CONNECTION_CONNECTED = "pipes.account_connection.connected" + PIPES_ACCOUNT_CONNECTION_CONNECTION_FAILED = ( + "pipes.account_connection.connection_failed" + ) + PIPES_ACCOUNT_CONNECTION_DISCONNECTED = "pipes.account_connection.disconnected" + PIPES_ACCOUNT_CONNECTION_REAUTHORIZATION_NEEDED = ( + "pipes.account_connection.reauthorization_needed" + ) PIPES_CONNECTED_ACCOUNT_CONNECTED = "pipes.connected_account.connected" PIPES_CONNECTED_ACCOUNT_CONNECTION_FAILED = ( "pipes.connected_account.connection_failed" @@ -228,6 +237,11 @@ def _missing_(cls, value: object) -> CreateWebhookEndpointEvents | None: "permission.created", "permission.deleted", "permission.updated", + "pipes.account_connection.add_failed", + "pipes.account_connection.connected", + "pipes.account_connection.connection_failed", + "pipes.account_connection.disconnected", + "pipes.account_connection.reauthorization_needed", "pipes.connected_account.connected", "pipes.connected_account.connection_failed", "pipes.connected_account.disconnected", diff --git a/src/workos/common/models/data_integration_installation_connection_role.py b/src/workos/common/models/data_integration_installation_connection_role.py new file mode 100644 index 000000000..d619922a9 --- /dev/null +++ b/src/workos/common/models/data_integration_installation_connection_role.py @@ -0,0 +1,8 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .connected_account_connection_role import ConnectedAccountConnectionRole + +DataIntegrationInstallationConnectionRole: TypeAlias = ConnectedAccountConnectionRole +__all__ = ["DataIntegrationInstallationConnectionRole"] diff --git a/src/workos/common/models/data_integrations_get_data_integration_authorize_url_request_connection_owner.py b/src/workos/common/models/data_integrations_get_data_integration_authorize_url_request_connection_owner.py new file mode 100644 index 000000000..f114ebc08 --- /dev/null +++ b/src/workos/common/models/data_integrations_get_data_integration_authorize_url_request_connection_owner.py @@ -0,0 +1,10 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .create_data_integration_ownership import CreateDataIntegrationOwnership + +DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner: TypeAlias = ( + CreateDataIntegrationOwnership +) +__all__ = ["DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner"] diff --git a/src/workos/common/models/data_integrations_list_response.py b/src/workos/common/models/data_integrations_list_response.py new file mode 100644 index 000000000..a8482582a --- /dev/null +++ b/src/workos/common/models/data_integrations_list_response.py @@ -0,0 +1,43 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Literal, cast + +from workos._types import _raise_deserialize_error + +from .data_integrations_list_response_data import DataIntegrationsListResponseData + + +@dataclass(slots=True) +class DataIntegrationsListResponse: + """Data Integrations List Response model.""" + + object: Literal["list"] + """Indicates this is a list response.""" + data: list[DataIntegrationsListResponseData] + """A list of [providers](https://workos.com/docs/reference/pipes/provider), each including the legacy `connected_account` field and the additive `connected_accounts` collection.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsListResponse: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "list"), + data=[ + DataIntegrationsListResponseData.from_dict( + cast(dict[str, Any], item) + ) + for item in cast(list[Any], data["data"]) + ], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("DataIntegrationsListResponse", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["data"] = [item.to_dict() for item in self.data] + return result diff --git a/src/workos/common/models/data_integrations_list_response_data.py b/src/workos/common/models/data_integrations_list_response_data.py new file mode 100644 index 000000000..84a9e8356 --- /dev/null +++ b/src/workos/common/models/data_integrations_list_response_data.py @@ -0,0 +1,146 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any, Literal, cast + +from workos._types import _raise_deserialize_error + +from .data_integrations_list_response_data_auth_methods import ( + DataIntegrationsListResponseDataAuthMethods, +) +from .data_integrations_list_response_data_connected_account import ( + DataIntegrationsListResponseDataConnectedAccount, +) +from .data_integrations_list_response_data_connection_owner import ( + DataIntegrationsListResponseDataConnectionOwner, +) +from .data_integrations_list_response_data_ownership import ( + DataIntegrationsListResponseDataOwnership, +) + + +@dataclass(slots=True) +class DataIntegrationsListResponseData: + """Data Integrations List Response Data model.""" + + object: Literal["data_provider"] + """Distinguishes the data provider object.""" + id: str + """The unique identifier of the provider.""" + name: str + """The display name of the provider (e.g., \"GitHub\", \"Slack\").""" + description: str | None + """A description of the provider explaining how it will be used, if configured.""" + slug: str + """The slug identifier used in API calls (e.g., `github`, `slack`, `notion`).""" + integration_type: str + """The type of integration (e.g., `github`, `slack`).""" + credentials_type: str + """The type of credentials used by the provider (e.g., `oauth2`).""" + scopes: list[str] | None + """The OAuth scopes configured for this provider, or `null` if none are configured.""" + connection_owner: DataIntegrationsListResponseDataConnectionOwner + """Who owns connections made through this provider: `user` for connections owned by individual users, or `organization` for a connection shared by every member of the organization. A provider row can exist before any connected account does.""" + created_at: str + """The timestamp when the provider was created.""" + updated_at: str + """The timestamp when the provider was last updated.""" + connected_account: DataIntegrationsListResponseDataConnectedAccount | None + """The user's compatibility [connected account](https://workos.com/docs/reference/pipes/connected-account) for this provider, or `null` when the compatibility slot is empty. This legacy field never selects a standard connection.""" + connected_accounts: list[DataIntegrationsListResponseDataConnectedAccount] + """The user's connected accounts for this provider in the requested ownership context. This contains only the compatibility connection unless `supports_multiple_connections` is `true`.""" + auth_methods: list[DataIntegrationsListResponseDataAuthMethods] | None = None + """The authentication methods supported by this provider (`oauth`, `api_key`, `client_credentials`, or a combination). Defaults to `[\"oauth\"]` if absent.""" + ownership: DataIntegrationsListResponseDataOwnership | None = None + """Use `connection_owner` instead. Legacy spelling of the same value: `userland_user` corresponds to `connection_owner: \"user\"` and `organization` to `connection_owner: \"organization\"`. + + .. deprecated:: This field is deprecated.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsListResponseData: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "data_provider"), + id=data["id"], + name=data["name"], + description=data["description"], + slug=data["slug"], + integration_type=data["integration_type"], + credentials_type=data["credentials_type"], + scopes=data["scopes"], + connection_owner=DataIntegrationsListResponseDataConnectionOwner( + data["connection_owner"] + ), + created_at=data["created_at"], + updated_at=data["updated_at"], + connected_account=DataIntegrationsListResponseDataConnectedAccount.from_dict( + cast(dict[str, Any], _v_connected_account) + ) + if (_v_connected_account := data["connected_account"]) is not None + else None, + connected_accounts=[ + DataIntegrationsListResponseDataConnectedAccount.from_dict( + cast(dict[str, Any], item) + ) + for item in cast(list[Any], data["connected_accounts"]) + ], + auth_methods=[ + DataIntegrationsListResponseDataAuthMethods(item) + for item in cast(list[Any], _v_auth_methods) + ] + if (_v_auth_methods := data.get("auth_methods")) is not None + else None, + ownership=DataIntegrationsListResponseDataOwnership(_v_ownership) + if (_v_ownership := data.get("ownership")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("DataIntegrationsListResponseData", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["name"] = self.name + if self.description is not None: + result["description"] = self.description + else: + result["description"] = None + result["slug"] = self.slug + result["integration_type"] = self.integration_type + result["credentials_type"] = self.credentials_type + if self.scopes is not None: + result["scopes"] = self.scopes + else: + result["scopes"] = None + result["connection_owner"] = ( + self.connection_owner.value + if isinstance(self.connection_owner, Enum) + else self.connection_owner + ) + result["created_at"] = self.created_at + result["updated_at"] = self.updated_at + if self.connected_account is not None: + result["connected_account"] = self.connected_account.to_dict() + else: + result["connected_account"] = None + result["connected_accounts"] = [ + item.to_dict() for item in self.connected_accounts + ] + if self.auth_methods is not None: + result["auth_methods"] = [ + item.value if isinstance(item, Enum) else item + for item in self.auth_methods + ] + if self.ownership is not None: + result["ownership"] = ( + self.ownership.value + if isinstance(self.ownership, Enum) + else self.ownership + ) + return result diff --git a/src/workos/common/models/data_integrations_list_response_data_connected_account.py b/src/workos/common/models/data_integrations_list_response_data_connected_account.py new file mode 100644 index 000000000..fa3e57c88 --- /dev/null +++ b/src/workos/common/models/data_integrations_list_response_data_connected_account.py @@ -0,0 +1,162 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any, Literal + +from workos._types import _raise_deserialize_error + +from .data_integrations_list_response_data_connected_account_auth_method import ( + DataIntegrationsListResponseDataConnectedAccountAuthMethod, +) +from .data_integrations_list_response_data_connected_account_connection_role import ( + DataIntegrationsListResponseDataConnectedAccountConnectionRole, +) +from .data_integrations_list_response_data_connected_account_state import ( + DataIntegrationsListResponseDataConnectedAccountState, +) + + +@dataclass(slots=True) +class DataIntegrationsListResponseDataConnectedAccount: + """Data Integrations List Response Data Connected Account model.""" + + object: Literal["connected_account"] + """Distinguishes the connected account object.""" + id: str + """The unique identifier of the connected account.""" + connection_role: DataIntegrationsListResponseDataConnectedAccountConnectionRole + """Whether this row is the compatibility connection visible to undeclared clients or a standard peer for plural-aware clients. The role does not indicate preference or creation order.""" + account_identifier: str | None + """A best-effort identifier for the provider account this connection points at. It is correlation metadata, not the connection identifier or a selector.""" + account_display_name: str | None + """A mutable, non-unique display name for the provider account connection.""" + user_id: str | None + """The [User](https://workos.com/docs/reference/authkit/user) identifier associated with this connection.""" + organization_id: str | None + """The [Organization](https://workos.com/docs/reference/organization) identifier associated with this connection, or `null` if not scoped to an organization.""" + scopes: list[str] + """The OAuth scopes granted for this connection.""" + state: DataIntegrationsListResponseDataConnectedAccountState + """The state of the connected account: +- `connected`: The connection is active and tokens are valid. +- `needs_reauthorization`: The user needs to reauthorize the connection, typically because required scopes have changed. +- `disconnected`: The connection has been disconnected.""" + created_at: str + """The timestamp when the connection was created.""" + updated_at: str + """The timestamp when the connection was last updated.""" + auth_method: DataIntegrationsListResponseDataConnectedAccountAuthMethod | None = ( + None + ) + """The authentication method used for this connection (`oauth`, `api_key`, or `client_credentials`). Defaults to `oauth` if absent.""" + api_key_last_4: str | None = None + """The last four characters of the API key, or `null` for OAuth connections.""" + client_id: str | None = None + """The client ID supplied for this connection. Only present when `auth_method` is `client_credentials`.""" + client_secret_last_4: str | None = None + """The last four characters of the client secret supplied for this connection, or `null` when it can't be read. Only present when `auth_method` is `client_credentials`.""" + config: dict[str, str] | None = None + """The connection-level configuration values stored for this connection — the fields the provider declares at `installation` scope, excluding any it declares as secret. Only present when `auth_method` is `client_credentials`.""" + userland_user_id: str | None = None + """Use `user_id` instead. + + .. deprecated:: This field is deprecated.""" + + @classmethod + def from_dict( + cls, data: dict[str, Any] + ) -> DataIntegrationsListResponseDataConnectedAccount: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "connected_account"), + id=data["id"], + connection_role=DataIntegrationsListResponseDataConnectedAccountConnectionRole( + data["connection_role"] + ), + account_identifier=data["account_identifier"], + account_display_name=data["account_display_name"], + user_id=data["user_id"], + organization_id=data["organization_id"], + scopes=data["scopes"], + state=DataIntegrationsListResponseDataConnectedAccountState( + data["state"] + ), + created_at=data["created_at"], + updated_at=data["updated_at"], + auth_method=DataIntegrationsListResponseDataConnectedAccountAuthMethod( + _v_auth_method + ) + if (_v_auth_method := data.get("auth_method")) is not None + else None, + api_key_last_4=data.get("api_key_last_4"), + client_id=data.get("client_id"), + client_secret_last_4=data.get("client_secret_last_4"), + config=data.get("config"), + userland_user_id=data.get("userlandUserId"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error( + "DataIntegrationsListResponseDataConnectedAccount", e + ) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["connection_role"] = ( + self.connection_role.value + if isinstance(self.connection_role, Enum) + else self.connection_role + ) + if self.account_identifier is not None: + result["account_identifier"] = self.account_identifier + else: + result["account_identifier"] = None + if self.account_display_name is not None: + result["account_display_name"] = self.account_display_name + else: + result["account_display_name"] = None + if self.user_id is not None: + result["user_id"] = self.user_id + else: + result["user_id"] = None + if self.organization_id is not None: + result["organization_id"] = self.organization_id + else: + result["organization_id"] = None + result["scopes"] = self.scopes + result["state"] = ( + self.state.value if isinstance(self.state, Enum) else self.state + ) + result["created_at"] = self.created_at + result["updated_at"] = self.updated_at + if self.auth_method is not None: + result["auth_method"] = ( + self.auth_method.value + if isinstance(self.auth_method, Enum) + else self.auth_method + ) + if self.api_key_last_4 is not None: + result["api_key_last_4"] = self.api_key_last_4 + else: + result["api_key_last_4"] = None + if self.client_id is not None: + result["client_id"] = self.client_id + else: + result["client_id"] = None + if self.client_secret_last_4 is not None: + result["client_secret_last_4"] = self.client_secret_last_4 + else: + result["client_secret_last_4"] = None + if self.config is not None: + result["config"] = self.config + if self.userland_user_id is not None: + result["userlandUserId"] = self.userland_user_id + else: + result["userlandUserId"] = None + return result diff --git a/src/workos/common/models/data_integrations_list_response_data_connected_account_connection_role.py b/src/workos/common/models/data_integrations_list_response_data_connected_account_connection_role.py new file mode 100644 index 000000000..c30af3adb --- /dev/null +++ b/src/workos/common/models/data_integrations_list_response_data_connected_account_connection_role.py @@ -0,0 +1,10 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .connected_account_connection_role import ConnectedAccountConnectionRole + +DataIntegrationsListResponseDataConnectedAccountConnectionRole: TypeAlias = ( + ConnectedAccountConnectionRole +) +__all__ = ["DataIntegrationsListResponseDataConnectedAccountConnectionRole"] diff --git a/src/workos/common/models/data_integrations_list_response_data_connection_owner.py b/src/workos/common/models/data_integrations_list_response_data_connection_owner.py new file mode 100644 index 000000000..9286682ae --- /dev/null +++ b/src/workos/common/models/data_integrations_list_response_data_connection_owner.py @@ -0,0 +1,10 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .create_data_integration_ownership import CreateDataIntegrationOwnership + +DataIntegrationsListResponseDataConnectionOwner: TypeAlias = ( + CreateDataIntegrationOwnership +) +__all__ = ["DataIntegrationsListResponseDataConnectionOwner"] diff --git a/src/workos/common/models/pipe_connected_account.py b/src/workos/common/models/pipe_connected_account.py index 345b31fd0..348465b7c 100644 --- a/src/workos/common/models/pipe_connected_account.py +++ b/src/workos/common/models/pipe_connected_account.py @@ -9,6 +9,8 @@ from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error +from .pipe_connected_account_auth_method import PipeConnectedAccountAuthMethod +from .pipe_connected_account_connection_role import PipeConnectedAccountConnectionRole from .pipe_connected_account_state import PipeConnectedAccountState @@ -36,6 +38,16 @@ class PipeConnectedAccount: """An ISO 8601 timestamp.""" updated_at: datetime """An ISO 8601 timestamp.""" + connection_role: PipeConnectedAccountConnectionRole | None = None + """Whether this is the compatibility connection visible to undeclared clients or a standard connection for plural-aware clients. Historical events may omit this field.""" + account_identifier: str | None = None + """A best-effort identifier for the provider account this connection points at. It is not the connection identifier or a selector. Historical events may omit this field.""" + account_display_name: str | None = None + """A mutable, non-unique display name for the provider account connection. Historical events may omit this field.""" + auth_method: PipeConnectedAccountAuthMethod | None = None + """How the connection authenticates. Historical events may omit this field.""" + api_key_last_4: str | None = None + """The last four characters of the API key, or null for other authentication methods. Historical events may omit this field.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> PipeConnectedAccount: @@ -52,6 +64,15 @@ def from_dict(cls, data: dict[str, Any]) -> PipeConnectedAccount: state=PipeConnectedAccountState(data["state"]), created_at=_parse_datetime(data["created_at"]), updated_at=_parse_datetime(data["updated_at"]), + connection_role=PipeConnectedAccountConnectionRole(_v_connection_role) + if (_v_connection_role := data.get("connection_role")) is not None + else None, + account_identifier=data.get("account_identifier"), + account_display_name=data.get("account_display_name"), + auth_method=PipeConnectedAccountAuthMethod(_v_auth_method) + if (_v_auth_method := data.get("auth_method")) is not None + else None, + api_key_last_4=data.get("api_key_last_4"), ) except (KeyError, ValueError) as e: _raise_deserialize_error("PipeConnectedAccount", e) @@ -77,4 +98,28 @@ def to_dict(self) -> dict[str, Any]: ) result["created_at"] = _format_datetime(self.created_at) result["updated_at"] = _format_datetime(self.updated_at) + if self.connection_role is not None: + result["connection_role"] = ( + self.connection_role.value + if isinstance(self.connection_role, Enum) + else self.connection_role + ) + if self.account_identifier is not None: + result["account_identifier"] = self.account_identifier + else: + result["account_identifier"] = None + if self.account_display_name is not None: + result["account_display_name"] = self.account_display_name + else: + result["account_display_name"] = None + if self.auth_method is not None: + result["auth_method"] = ( + self.auth_method.value + if isinstance(self.auth_method, Enum) + else self.auth_method + ) + if self.api_key_last_4 is not None: + result["api_key_last_4"] = self.api_key_last_4 + else: + result["api_key_last_4"] = None return result diff --git a/src/workos/common/models/pipe_connected_account_auth_method.py b/src/workos/common/models/pipe_connected_account_auth_method.py new file mode 100644 index 000000000..aec489f86 --- /dev/null +++ b/src/workos/common/models/pipe_connected_account_auth_method.py @@ -0,0 +1,8 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .connected_account_auth_method import ConnectedAccountAuthMethod + +PipeConnectedAccountAuthMethod: TypeAlias = ConnectedAccountAuthMethod +__all__ = ["PipeConnectedAccountAuthMethod"] diff --git a/src/workos/common/models/pipe_connected_account_connection_role.py b/src/workos/common/models/pipe_connected_account_connection_role.py new file mode 100644 index 000000000..8a2e85496 --- /dev/null +++ b/src/workos/common/models/pipe_connected_account_connection_role.py @@ -0,0 +1,8 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .connected_account_connection_role import ConnectedAccountConnectionRole + +PipeConnectedAccountConnectionRole: TypeAlias = ConnectedAccountConnectionRole +__all__ = ["PipeConnectedAccountConnectionRole"] diff --git a/src/workos/common/models/pipes_account_connection_add_failed.py b/src/workos/common/models/pipes_account_connection_add_failed.py new file mode 100644 index 000000000..9b15ba9b5 --- /dev/null +++ b/src/workos/common/models/pipes_account_connection_add_failed.py @@ -0,0 +1,61 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .event_context import EventContext +from .pipes_account_connection_add_failed_data import ( + PipesAccountConnectionAddFailedData, +) + + +@dataclass(slots=True) +class PipesAccountConnectionAddFailed: + """Pipes Account Connection Add Failed model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["pipes.account_connection.add_failed"] + data: PipesAccountConnectionAddFailedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> PipesAccountConnectionAddFailed: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "pipes.account_connection.add_failed"), + data=PipesAccountConnectionAddFailedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("PipesAccountConnectionAddFailed", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/pipes_account_connection_add_failed_data.py b/src/workos/common/models/pipes_account_connection_add_failed_data.py new file mode 100644 index 000000000..588cd526d --- /dev/null +++ b/src/workos/common/models/pipes_account_connection_add_failed_data.py @@ -0,0 +1,91 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + + +@dataclass(slots=True) +class PipesAccountConnectionAddFailedData: + """The event payload.""" + + object: Literal["connection_failed"] + """The object type.""" + data_integration_id: str + """The unique ID of the data integration.""" + provider_slug: str + """The provider slug for this connection attempt.""" + user_id: str | None + """The ID of the User the connection attempt belongs to.""" + organization_id: str | None + """The ID of the Organization the connection attempt belongs to.""" + error_code: str + """A machine-readable error code for the failure.""" + error_reason: str | None + """A human-readable explanation of the failure.""" + provider_error: str | None + """The raw error code returned by the OAuth provider.""" + provider_error_description: str | None + """The raw error description returned by the OAuth provider.""" + created_at: datetime + """An ISO 8601 timestamp.""" + account_identifier: str | None = None + """A best-effort identifier for the provider account involved in the attempt. It is not a connection identifier or selector. Historical events may omit this field.""" + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> PipesAccountConnectionAddFailedData: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "connection_failed"), + data_integration_id=data["data_integration_id"], + provider_slug=data["provider_slug"], + user_id=data["user_id"], + organization_id=data["organization_id"], + error_code=data["error_code"], + error_reason=data["error_reason"], + provider_error=data["provider_error"], + provider_error_description=data["provider_error_description"], + created_at=_parse_datetime(data["created_at"]), + account_identifier=data.get("account_identifier"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("PipesAccountConnectionAddFailedData", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["data_integration_id"] = self.data_integration_id + result["provider_slug"] = self.provider_slug + if self.user_id is not None: + result["user_id"] = self.user_id + else: + result["user_id"] = None + if self.organization_id is not None: + result["organization_id"] = self.organization_id + else: + result["organization_id"] = None + result["error_code"] = self.error_code + if self.error_reason is not None: + result["error_reason"] = self.error_reason + else: + result["error_reason"] = None + if self.provider_error is not None: + result["provider_error"] = self.provider_error + else: + result["provider_error"] = None + if self.provider_error_description is not None: + result["provider_error_description"] = self.provider_error_description + else: + result["provider_error_description"] = None + result["created_at"] = _format_datetime(self.created_at) + if self.account_identifier is not None: + result["account_identifier"] = self.account_identifier + else: + result["account_identifier"] = None + return result diff --git a/src/workos/common/models/pipes_account_connection_connected.py b/src/workos/common/models/pipes_account_connection_connected.py new file mode 100644 index 000000000..206520bf9 --- /dev/null +++ b/src/workos/common/models/pipes_account_connection_connected.py @@ -0,0 +1,57 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .event_context import EventContext +from .pipe_connected_account import PipeConnectedAccount + + +@dataclass(slots=True) +class PipesAccountConnectionConnected: + """Pipes Account Connection Connected model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["pipes.account_connection.connected"] + data: PipeConnectedAccount + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> PipesAccountConnectionConnected: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "pipes.account_connection.connected"), + data=PipeConnectedAccount.from_dict(cast(dict[str, Any], data["data"])), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("PipesAccountConnectionConnected", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/pipes_account_connection_connection_failed.py b/src/workos/common/models/pipes_account_connection_connection_failed.py new file mode 100644 index 000000000..dd4feab26 --- /dev/null +++ b/src/workos/common/models/pipes_account_connection_connection_failed.py @@ -0,0 +1,61 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .event_context import EventContext +from .pipes_account_connection_connection_failed_data import ( + PipesAccountConnectionConnectionFailedData, +) + + +@dataclass(slots=True) +class PipesAccountConnectionConnectionFailed: + """Pipes Account Connection Connection Failed model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["pipes.account_connection.connection_failed"] + data: PipesAccountConnectionConnectionFailedData + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> PipesAccountConnectionConnectionFailed: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "pipes.account_connection.connection_failed"), + data=PipesAccountConnectionConnectionFailedData.from_dict( + cast(dict[str, Any], data["data"]) + ), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("PipesAccountConnectionConnectionFailed", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/pipes_account_connection_connection_failed_data.py b/src/workos/common/models/pipes_account_connection_connection_failed_data.py new file mode 100644 index 000000000..1893a36eb --- /dev/null +++ b/src/workos/common/models/pipes_account_connection_connection_failed_data.py @@ -0,0 +1,11 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias + +from .pipes_account_connection_add_failed_data import ( + PipesAccountConnectionAddFailedData, +) + +PipesAccountConnectionConnectionFailedData: TypeAlias = ( + PipesAccountConnectionAddFailedData +) diff --git a/src/workos/common/models/pipes_account_connection_disconnected.py b/src/workos/common/models/pipes_account_connection_disconnected.py new file mode 100644 index 000000000..1f7109c03 --- /dev/null +++ b/src/workos/common/models/pipes_account_connection_disconnected.py @@ -0,0 +1,57 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .event_context import EventContext +from .pipe_connected_account import PipeConnectedAccount + + +@dataclass(slots=True) +class PipesAccountConnectionDisconnected: + """Pipes Account Connection Disconnected model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["pipes.account_connection.disconnected"] + data: PipeConnectedAccount + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> PipesAccountConnectionDisconnected: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get("event", "pipes.account_connection.disconnected"), + data=PipeConnectedAccount.from_dict(cast(dict[str, Any], data["data"])), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("PipesAccountConnectionDisconnected", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/pipes_account_connection_reauthorization_needed.py b/src/workos/common/models/pipes_account_connection_reauthorization_needed.py new file mode 100644 index 000000000..890701aab --- /dev/null +++ b/src/workos/common/models/pipes_account_connection_reauthorization_needed.py @@ -0,0 +1,61 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Literal, cast + +from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error + +from .event_context import EventContext +from .pipe_connected_account import PipeConnectedAccount + + +@dataclass(slots=True) +class PipesAccountConnectionReauthorizationNeeded: + """Pipes Account Connection Reauthorization Needed model.""" + + object: Literal["event"] + """Distinguishes the Event object.""" + id: str + """Unique identifier for the event.""" + event: Literal["pipes.account_connection.reauthorization_needed"] + data: PipeConnectedAccount + """The event payload.""" + created_at: datetime + """An ISO 8601 timestamp.""" + context: EventContext | None = None + + @classmethod + def from_dict( + cls, data: dict[str, Any] + ) -> PipesAccountConnectionReauthorizationNeeded: + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "event"), + id=data["id"], + event=data.get( + "event", "pipes.account_connection.reauthorization_needed" + ), + data=PipeConnectedAccount.from_dict(cast(dict[str, Any], data["data"])), + created_at=_parse_datetime(data["created_at"]), + context=EventContext.from_dict(cast(dict[str, Any], _v_context)) + if (_v_context := data.get("context")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("PipesAccountConnectionReauthorizationNeeded", e) + + def to_dict(self) -> dict[str, Any]: + """Serialize to a dictionary.""" + result: dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["event"] = self.event + result["data"] = self.data.to_dict() + result["created_at"] = _format_datetime(self.created_at) + if self.context is not None: + result["context"] = self.context.to_dict() + return result diff --git a/src/workos/common/models/pipes_connected_account_connection_failed_data.py b/src/workos/common/models/pipes_connected_account_connection_failed_data.py index fdee3e188..a2ac21947 100644 --- a/src/workos/common/models/pipes_connected_account_connection_failed_data.py +++ b/src/workos/common/models/pipes_connected_account_connection_failed_data.py @@ -1,86 +1,11 @@ # This file is auto-generated by oagen. Do not edit. -from __future__ import annotations +from typing import TypeAlias -from dataclasses import dataclass -from datetime import datetime -from typing import Any, Literal +from .pipes_account_connection_add_failed_data import ( + PipesAccountConnectionAddFailedData, +) -from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error - - -@dataclass(slots=True) -class PipesConnectedAccountConnectionFailedData: - """The event payload.""" - - object: Literal["connection_failed"] - """The object type.""" - data_integration_id: str - """The unique ID of the data integration.""" - provider_slug: str - """The provider slug for this connection attempt.""" - user_id: str | None - """The ID of the User the connection attempt belongs to.""" - organization_id: str | None - """The ID of the Organization the connection attempt belongs to.""" - error_code: str - """A machine-readable error code for the failure.""" - error_reason: str | None - """A human-readable explanation of the failure.""" - provider_error: str | None - """The raw error code returned by the OAuth provider.""" - provider_error_description: str | None - """The raw error description returned by the OAuth provider.""" - created_at: datetime - """An ISO 8601 timestamp.""" - - @classmethod - def from_dict( - cls, data: dict[str, Any] - ) -> PipesConnectedAccountConnectionFailedData: - """Deserialize from a dictionary.""" - try: - return cls( - object=data.get("object", "connection_failed"), - data_integration_id=data["data_integration_id"], - provider_slug=data["provider_slug"], - user_id=data["user_id"], - organization_id=data["organization_id"], - error_code=data["error_code"], - error_reason=data["error_reason"], - provider_error=data["provider_error"], - provider_error_description=data["provider_error_description"], - created_at=_parse_datetime(data["created_at"]), - ) - except (KeyError, ValueError) as e: - _raise_deserialize_error("PipesConnectedAccountConnectionFailedData", e) - - def to_dict(self) -> dict[str, Any]: - """Serialize to a dictionary.""" - result: dict[str, Any] = {} - result["object"] = self.object - result["data_integration_id"] = self.data_integration_id - result["provider_slug"] = self.provider_slug - if self.user_id is not None: - result["user_id"] = self.user_id - else: - result["user_id"] = None - if self.organization_id is not None: - result["organization_id"] = self.organization_id - else: - result["organization_id"] = None - result["error_code"] = self.error_code - if self.error_reason is not None: - result["error_reason"] = self.error_reason - else: - result["error_reason"] = None - if self.provider_error is not None: - result["provider_error"] = self.provider_error - else: - result["provider_error"] = None - if self.provider_error_description is not None: - result["provider_error_description"] = self.provider_error_description - else: - result["provider_error_description"] = None - result["created_at"] = _format_datetime(self.created_at) - return result +PipesConnectedAccountConnectionFailedData: TypeAlias = ( + PipesAccountConnectionAddFailedData +) diff --git a/src/workos/common/models/resource_export_completed_data_resource_type.py b/src/workos/common/models/resource_export_completed_data_resource_type.py index 4d08f5cc4..d9e58dc14 100644 --- a/src/workos/common/models/resource_export_completed_data_resource_type.py +++ b/src/workos/common/models/resource_export_completed_data_resource_type.py @@ -17,6 +17,7 @@ class ResourceExportCompletedDataResourceType(str, Enum): SESSIONS = "sessions" AUDIT_LOG_EVENTS = "auditLogEvents" CONNECTIONS = "connections" + DIRECTORY_USERS = "directoryUsers" @classmethod def _missing_(cls, value: object) -> ResourceExportCompletedDataResourceType | None: @@ -29,5 +30,11 @@ def _missing_(cls, value: object) -> ResourceExportCompletedDataResourceType | N ResourceExportCompletedDataResourceTypeLiteral: TypeAlias = Literal[ - "users", "organizations", "events", "sessions", "auditLogEvents", "connections" + "users", + "organizations", + "events", + "sessions", + "auditLogEvents", + "connections", + "directoryUsers", ] diff --git a/src/workos/connect/_resource.py b/src/workos/connect/_resource.py index 0c65c13d4..9f06c2ebe 100644 --- a/src/workos/connect/_resource.py +++ b/src/workos/connect/_resource.py @@ -110,9 +110,9 @@ def list_applications( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. registration_types: Filter Connect Applications by registration type. Specify multiple as a comma-separated list (e.g. `registration_types=dynamic,authenticated`). Defaults to `authenticated` only when not specified. organization_id: Filter Connect Applications by organization ID. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -552,9 +552,9 @@ async def list_applications( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. registration_types: Filter Connect Applications by registration type. Specify multiple as a comma-separated list (e.g. `registration_types=dynamic,authenticated`). Defaults to `authenticated` only when not specified. organization_id: Filter Connect Applications by organization ID. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. diff --git a/src/workos/directory_sync/_resource.py b/src/workos/directory_sync/_resource.py index 48531284a..0a7bdd45c 100644 --- a/src/workos/directory_sync/_resource.py +++ b/src/workos/directory_sync/_resource.py @@ -152,9 +152,9 @@ def list_groups( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. directory: Unique identifier of the WorkOS Directory. This value can be obtained from the WorkOS dashboard or from the WorkOS API. user: Unique identifier of the WorkOS Directory User. This value can be obtained from the WorkOS API. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -240,9 +240,9 @@ def list_users( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. directory: Unique identifier of the WorkOS Directory. This value can be obtained from the WorkOS dashboard or from the WorkOS API. group: Unique identifier of the WorkOS Directory Group. This value can be obtained from the WorkOS API. idp_id: Filter Directory Users by the identity provider's unique identifier (`idp_id`). Requires the `directory` parameter to also be provided. @@ -451,9 +451,9 @@ async def list_groups( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. directory: Unique identifier of the WorkOS Directory. This value can be obtained from the WorkOS dashboard or from the WorkOS API. user: Unique identifier of the WorkOS Directory User. This value can be obtained from the WorkOS API. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -539,9 +539,9 @@ async def list_users( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. directory: Unique identifier of the WorkOS Directory. This value can be obtained from the WorkOS dashboard or from the WorkOS API. group: Unique identifier of the WorkOS Directory Group. This value can be obtained from the WorkOS API. idp_id: Filter Directory Users by the identity provider's unique identifier (`idp_id`). Requires the `directory` parameter to also be provided. diff --git a/src/workos/events/_resource.py b/src/workos/events/_resource.py index fcb68d34d..a5caef779 100644 --- a/src/workos/events/_resource.py +++ b/src/workos/events/_resource.py @@ -39,9 +39,9 @@ def list_events( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. events: Filter events by one or more event types (e.g. `dsync.user.created`). range_start: ISO-8601 date string to filter events created after this date. range_end: ISO-8601 date string to filter events created before this date. @@ -109,9 +109,9 @@ async def list_events( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. events: Filter events by one or more event types (e.g. `dsync.user.created`). range_start: ISO-8601 date string to filter events created after this date. range_end: ISO-8601 date string to filter events created before this date. diff --git a/src/workos/events/models/event_schema.py b/src/workos/events/models/event_schema.py index eded15c97..886e0966e 100644 --- a/src/workos/events/models/event_schema.py +++ b/src/workos/events/models/event_schema.py @@ -144,6 +144,21 @@ from workos.common.models.permission_created import PermissionCreated from workos.common.models.permission_deleted import PermissionDeleted from workos.common.models.permission_updated import PermissionUpdated +from workos.common.models.pipes_account_connection_add_failed import ( + PipesAccountConnectionAddFailed, +) +from workos.common.models.pipes_account_connection_connected import ( + PipesAccountConnectionConnected, +) +from workos.common.models.pipes_account_connection_connection_failed import ( + PipesAccountConnectionConnectionFailed, +) +from workos.common.models.pipes_account_connection_disconnected import ( + PipesAccountConnectionDisconnected, +) +from workos.common.models.pipes_account_connection_reauthorization_needed import ( + PipesAccountConnectionReauthorizationNeeded, +) from workos.common.models.pipes_connected_account_connected import ( PipesConnectedAccountConnected, ) @@ -296,6 +311,11 @@ def to_dict(self) -> dict[str, Any]: PermissionCreated, PermissionDeleted, PermissionUpdated, + PipesAccountConnectionAddFailed, + PipesAccountConnectionConnected, + PipesAccountConnectionConnectionFailed, + PipesAccountConnectionDisconnected, + PipesAccountConnectionReauthorizationNeeded, PipesConnectedAccountConnected, PipesConnectedAccountConnectionFailed, PipesConnectedAccountDisconnected, @@ -426,6 +446,11 @@ class EventSchema: "permission.created": PermissionCreated, "permission.deleted": PermissionDeleted, "permission.updated": PermissionUpdated, + "pipes.account_connection.add_failed": PipesAccountConnectionAddFailed, + "pipes.account_connection.connected": PipesAccountConnectionConnected, + "pipes.account_connection.connection_failed": PipesAccountConnectionConnectionFailed, + "pipes.account_connection.disconnected": PipesAccountConnectionDisconnected, + "pipes.account_connection.reauthorization_needed": PipesAccountConnectionReauthorizationNeeded, "pipes.connected_account.connected": PipesConnectedAccountConnected, "pipes.connected_account.connection_failed": PipesConnectedAccountConnectionFailed, "pipes.connected_account.disconnected": PipesConnectedAccountDisconnected, diff --git a/src/workos/feature_flags/_resource.py b/src/workos/feature_flags/_resource.py index e5c12aae2..e3ee805ad 100644 --- a/src/workos/feature_flags/_resource.py +++ b/src/workos/feature_flags/_resource.py @@ -172,7 +172,7 @@ def add_flag_target( Enables a feature flag for a specific target in the current environment. Currently, supported targets include users and organizations. Args: - resource_id: The resource ID in format "user_" or "org_". + resource_id: The resource ID in format \"user_\" or \"org_\". slug: The unique slug identifier of the feature flag. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -202,7 +202,7 @@ def remove_flag_target( Removes a target from the feature flag's target list in the current environment. Currently, supported targets include users and organizations. Args: - resource_id: The resource ID in format "user_" or "org_". + resource_id: The resource ID in format \"user_\" or \"org_\". slug: The unique slug identifier of the feature flag. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -476,7 +476,7 @@ async def add_flag_target( Enables a feature flag for a specific target in the current environment. Currently, supported targets include users and organizations. Args: - resource_id: The resource ID in format "user_" or "org_". + resource_id: The resource ID in format \"user_\" or \"org_\". slug: The unique slug identifier of the feature flag. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -506,7 +506,7 @@ async def remove_flag_target( Removes a target from the feature flag's target list in the current environment. Currently, supported targets include users and organizations. Args: - resource_id: The resource ID in format "user_" or "org_". + resource_id: The resource ID in format \"user_\" or \"org_\". slug: The unique slug identifier of the feature flag. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. diff --git a/src/workos/groups/_resource.py b/src/workos/groups/_resource.py index c8a070e7f..aa11a988a 100644 --- a/src/workos/groups/_resource.py +++ b/src/workos/groups/_resource.py @@ -41,9 +41,9 @@ def list_organization_groups( Args: organization_id: The ID of the organization. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. search: Search groups by name or by group ID. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -249,9 +249,9 @@ def list_group_organization_memberships( organization_id: Unique identifier of the Organization. group_id: Unique identifier of the Group. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -398,9 +398,9 @@ async def list_organization_groups( Args: organization_id: The ID of the organization. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. search: Search groups by name or by group ID. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -606,9 +606,9 @@ async def list_group_organization_memberships( organization_id: Unique identifier of the Organization. group_id: Unique identifier of the Group. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: diff --git a/src/workos/multi_factor_auth/_resource.py b/src/workos/multi_factor_auth/_resource.py index 62610cf22..e466a385d 100644 --- a/src/workos/multi_factor_auth/_resource.py +++ b/src/workos/multi_factor_auth/_resource.py @@ -188,7 +188,7 @@ def challenge_factor( Args: id: The unique ID of the Authentication Factor to be challenged. - sms_template: A custom template for the SMS message. Use the {{code}} placeholder to include the verification code. + sms_template: A custom template for the SMS message. Use the {{code}} placeholder to include the verification code. Must not contain URLs or domain names. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -197,8 +197,8 @@ def challenge_factor( Raises: NotFoundError: If the resource is not found (404). UnprocessableEntityError: If the request data is unprocessable (422). - AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). + AuthenticationError: If the API key is invalid (401). ServerError: If the server returns a 5xx error. """ body: dict[str, Any] = { @@ -478,7 +478,7 @@ async def challenge_factor( Args: id: The unique ID of the Authentication Factor to be challenged. - sms_template: A custom template for the SMS message. Use the {{code}} placeholder to include the verification code. + sms_template: A custom template for the SMS message. Use the {{code}} placeholder to include the verification code. Must not contain URLs or domain names. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -487,8 +487,8 @@ async def challenge_factor( Raises: NotFoundError: If the resource is not found (404). UnprocessableEntityError: If the request data is unprocessable (422). - AuthenticationError: If the API key is invalid (401). RateLimitExceededError: If rate limited (429). + AuthenticationError: If the API key is invalid (401). ServerError: If the server returns a 5xx error. """ body: dict[str, Any] = { diff --git a/src/workos/multi_factor_auth/models/challenge_authentication_factor.py b/src/workos/multi_factor_auth/models/challenge_authentication_factor.py index 2ae351159..2d197d07a 100644 --- a/src/workos/multi_factor_auth/models/challenge_authentication_factor.py +++ b/src/workos/multi_factor_auth/models/challenge_authentication_factor.py @@ -13,7 +13,7 @@ class ChallengeAuthenticationFactor: """Challenge Authentication Factor model.""" sms_template: str | None = None - """A custom template for the SMS message. Use the {{code}} placeholder to include the verification code.""" + """A custom template for the SMS message. Use the {{code}} placeholder to include the verification code. Must not contain URLs or domain names.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> ChallengeAuthenticationFactor: diff --git a/src/workos/organization_membership/_resource.py b/src/workos/organization_membership/_resource.py index 1c020c597..4004facad 100644 --- a/src/workos/organization_membership/_resource.py +++ b/src/workos/organization_membership/_resource.py @@ -60,9 +60,9 @@ def list_organization_memberships( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. organization_id: The ID of the [organization](https://workos.com/docs/reference/organization) which the user belongs to. statuses: Filter by the status of the organization membership. Array including any of `active`, `inactive`, or `pending`. user_id: The ID of the [user](https://workos.com/docs/reference/authkit/user). @@ -337,9 +337,9 @@ def list_organization_membership_groups( Args: om_id: Unique identifier of the Organization Membership. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -395,9 +395,9 @@ async def list_organization_memberships( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. organization_id: The ID of the [organization](https://workos.com/docs/reference/organization) which the user belongs to. statuses: Filter by the status of the organization membership. Array including any of `active`, `inactive`, or `pending`. user_id: The ID of the [user](https://workos.com/docs/reference/authkit/user). @@ -672,9 +672,9 @@ async def list_organization_membership_groups( Args: om_id: Unique identifier of the Organization Membership. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: diff --git a/src/workos/organizations/_resource.py b/src/workos/organizations/_resource.py index e1d5671ea..bdc2b66e7 100644 --- a/src/workos/organizations/_resource.py +++ b/src/workos/organizations/_resource.py @@ -45,9 +45,9 @@ def list_organizations( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. domains: The domains of an Organization. Any Organization with a matching domain will be returned. search: Searchable text for an Organization. Matches against the organization name. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -341,9 +341,9 @@ def list_authorized_applications( Args: organization_id: The ID of the organization. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -580,9 +580,9 @@ async def list_organizations( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. domains: The domains of an Organization. Any Organization with a matching domain will be returned. search: Searchable text for an Organization. Matches against the organization name. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -876,9 +876,9 @@ async def list_authorized_applications( Args: organization_id: The ID of the organization. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: diff --git a/src/workos/organizations_data_providers/__init__.py b/src/workos/organizations_data_providers/__init__.py new file mode 100644 index 000000000..b773fa2da --- /dev/null +++ b/src/workos/organizations_data_providers/__init__.py @@ -0,0 +1,7 @@ +# This file is auto-generated by oagen. Do not edit. + +from ._resource import ( + AsyncOrganizationsDataProviders as AsyncOrganizationsDataProviders, +) +from ._resource import OrganizationsDataProviders as OrganizationsDataProviders +from .models import * diff --git a/src/workos/organizations_data_providers/_resource.py b/src/workos/organizations_data_providers/_resource.py new file mode 100644 index 000000000..f593134db --- /dev/null +++ b/src/workos/organizations_data_providers/_resource.py @@ -0,0 +1,584 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from .._client import AsyncWorkOSClient, WorkOSClient + +from workos.common.models.connected_account import ConnectedAccount +from workos.common.models.connected_account_input_state import ( + ConnectedAccountInputState, +) +from workos.common.models.data_integrations_list_response import ( + DataIntegrationsListResponse, +) + +from .._types import RequestOptions, enum_value + + +class OrganizationsDataProviders: + """Organizations Data Providers API resources.""" + + def __init__(self, client: WorkOSClient) -> None: + self._client = client + + def get_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + supports_multiple_connections: bool | None = None, + connected_account_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> ConnectedAccount: + """Get an organization connected account + + Retrieves an organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) for a specific provider. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the organization has several for this provider. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ConnectedAccount + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + "connected_account_id": connected_account_id, + }.items() + if v is not None + } + return self._client.request( + method="get", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + params=params, + model=ConnectedAccount, + request_options=request_options, + ) + + def create_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + access_token: str | None = None, + refresh_token: str | None = None, + expires_at: str | None = None, + scopes: list[str] | None = None, + state: ConnectedAccountInputState | str | None = None, + request_options: RequestOptions | None = None, + ) -> ConnectedAccount: + """Import an organization connected account + + Imports an organization-owned [connected account](https://workos.com/docs/reference/pipes/connected-account) by providing OAuth tokens directly. Use this to migrate existing connections or set up connections without going through the OAuth flow. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + access_token: The OAuth access token for the connected account. + refresh_token: The OAuth refresh token for the connected account. + expires_at: The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. + scopes: The OAuth scopes granted for this connection. + state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ConnectedAccount + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "access_token": access_token, + "refresh_token": refresh_token, + "expires_at": expires_at, + "scopes": scopes, + "state": enum_value(state) if state is not None else None, + }.items() + if v is not None + } + return self._client.request( + method="post", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + body=body, + model=ConnectedAccount, + request_options=request_options, + ) + + def update_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + access_token: str | None = None, + refresh_token: str | None = None, + expires_at: str | None = None, + scopes: list[str] | None = None, + state: ConnectedAccountInputState | str | None = None, + supports_multiple_connections: bool | None = None, + connected_account_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> ConnectedAccount: + """Update an organization connected account + + Updates an organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) tokens, scopes, or state for a specific provider. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + access_token: The OAuth access token for the connected account. + refresh_token: The OAuth refresh token for the connected account. + expires_at: The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. + scopes: The OAuth scopes granted for this connection. + state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to update. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ConnectedAccount + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "access_token": access_token, + "refresh_token": refresh_token, + "expires_at": expires_at, + "scopes": scopes, + "state": enum_value(state) if state is not None else None, + }.items() + if v is not None + } + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + "connected_account_id": connected_account_id, + }.items() + if v is not None + } + return self._client.request( + method="put", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + body=body, + params=params, + model=ConnectedAccount, + request_options=request_options, + ) + + def delete_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + supports_multiple_connections: bool | None = None, + connected_account_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an organization connected account + + Disconnects the organization's account for the provider, including removing any stored access and refresh tokens. A member will need to reauthorize if the organization wants to reconnect. This does not revoke access on the provider side. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to delete. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + "connected_account_id": connected_account_id, + }.items() + if v is not None + } + self._client.request( + method="delete", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + params=params, + request_options=request_options, + ) + + def list_organization_data_providers( + self, + organization_id: str, + *, + supports_multiple_connections: bool | None = None, + request_options: RequestOptions | None = None, + ) -> DataIntegrationsListResponse: + """List providers for an organization + + Retrieves the organization-owned providers configured for your environment and the organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) information for each. Providers owned by individual users are not included. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier to list providers and connected accounts for. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + DataIntegrationsListResponse + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + }.items() + if v is not None + } + return self._client.request( + method="get", + path=("organizations", str(organization_id), "data_providers"), + params=params, + model=DataIntegrationsListResponse, + request_options=request_options, + ) + + +class AsyncOrganizationsDataProviders: + """Organizations Data Providers API resources (async).""" + + def __init__(self, client: AsyncWorkOSClient) -> None: + self._client = client + + async def get_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + supports_multiple_connections: bool | None = None, + connected_account_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> ConnectedAccount: + """Get an organization connected account + + Retrieves an organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) for a specific provider. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the organization has several for this provider. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ConnectedAccount + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + "connected_account_id": connected_account_id, + }.items() + if v is not None + } + return await self._client.request( + method="get", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + params=params, + model=ConnectedAccount, + request_options=request_options, + ) + + async def create_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + access_token: str | None = None, + refresh_token: str | None = None, + expires_at: str | None = None, + scopes: list[str] | None = None, + state: ConnectedAccountInputState | str | None = None, + request_options: RequestOptions | None = None, + ) -> ConnectedAccount: + """Import an organization connected account + + Imports an organization-owned [connected account](https://workos.com/docs/reference/pipes/connected-account) by providing OAuth tokens directly. Use this to migrate existing connections or set up connections without going through the OAuth flow. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + access_token: The OAuth access token for the connected account. + refresh_token: The OAuth refresh token for the connected account. + expires_at: The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. + scopes: The OAuth scopes granted for this connection. + state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ConnectedAccount + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "access_token": access_token, + "refresh_token": refresh_token, + "expires_at": expires_at, + "scopes": scopes, + "state": enum_value(state) if state is not None else None, + }.items() + if v is not None + } + return await self._client.request( + method="post", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + body=body, + model=ConnectedAccount, + request_options=request_options, + ) + + async def update_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + access_token: str | None = None, + refresh_token: str | None = None, + expires_at: str | None = None, + scopes: list[str] | None = None, + state: ConnectedAccountInputState | str | None = None, + supports_multiple_connections: bool | None = None, + connected_account_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> ConnectedAccount: + """Update an organization connected account + + Updates an organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) tokens, scopes, or state for a specific provider. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + access_token: The OAuth access token for the connected account. + refresh_token: The OAuth refresh token for the connected account. + expires_at: The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. + scopes: The OAuth scopes granted for this connection. + state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to update. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ConnectedAccount + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "access_token": access_token, + "refresh_token": refresh_token, + "expires_at": expires_at, + "scopes": scopes, + "state": enum_value(state) if state is not None else None, + }.items() + if v is not None + } + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + "connected_account_id": connected_account_id, + }.items() + if v is not None + } + return await self._client.request( + method="put", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + body=body, + params=params, + model=ConnectedAccount, + request_options=request_options, + ) + + async def delete_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + supports_multiple_connections: bool | None = None, + connected_account_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an organization connected account + + Disconnects the organization's account for the provider, including removing any stored access and refresh tokens. A member will need to reauthorize if the organization wants to reconnect. This does not revoke access on the provider side. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to delete. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + "connected_account_id": connected_account_id, + }.items() + if v is not None + } + await self._client.request( + method="delete", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + params=params, + request_options=request_options, + ) + + async def list_organization_data_providers( + self, + organization_id: str, + *, + supports_multiple_connections: bool | None = None, + request_options: RequestOptions | None = None, + ) -> DataIntegrationsListResponse: + """List providers for an organization + + Retrieves the organization-owned providers configured for your environment and the organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) information for each. Providers owned by individual users are not included. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier to list providers and connected accounts for. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + DataIntegrationsListResponse + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + }.items() + if v is not None + } + return await self._client.request( + method="get", + path=("organizations", str(organization_id), "data_providers"), + params=params, + model=DataIntegrationsListResponse, + request_options=request_options, + ) diff --git a/src/workos/organizations_data_providers/models/__init__.py b/src/workos/organizations_data_providers/models/__init__.py new file mode 100644 index 000000000..5271119e8 --- /dev/null +++ b/src/workos/organizations_data_providers/models/__init__.py @@ -0,0 +1,14 @@ +# This file is auto-generated by oagen. Do not edit. + +from workos.common.models.connected_account_input import ( + ConnectedAccountInput as ConnectedAccountInput, +) +from workos.common.models.data_integrations_list_response import ( + DataIntegrationsListResponse as DataIntegrationsListResponse, +) +from workos.common.models.data_integrations_list_response_data import ( + DataIntegrationsListResponseData as DataIntegrationsListResponseData, +) +from workos.common.models.data_integrations_list_response_data_connected_account import ( + DataIntegrationsListResponseDataConnectedAccount as DataIntegrationsListResponseDataConnectedAccount, +) diff --git a/src/workos/pipes/_resource.py b/src/workos/pipes/_resource.py index a9287d729..d716edff8 100644 --- a/src/workos/pipes/_resource.py +++ b/src/workos/pipes/_resource.py @@ -17,9 +17,15 @@ from workos.common.models.create_data_integration_ownership import ( CreateDataIntegrationOwnership, ) +from workos.common.models.data_integrations_get_data_integration_authorize_url_request_connection_owner import ( + DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner, +) from workos.common.models.data_integrations_get_user_token_request_connection_owner import ( DataIntegrationsGetUserTokenRequestConnectionOwner, ) +from workos.common.models.data_integrations_list_response import ( + DataIntegrationsListResponse, +) from workos.common.models.data_integrations_upsert_api_key_request_connection_owner import ( DataIntegrationsUpsertApiKeyRequestConnectionOwner, ) @@ -41,7 +47,6 @@ DataIntegrationAuthorizeUrlResponse, DataIntegrationCredentialsInput, DataIntegrationCredentialsResponse, - DataIntegrationsListResponse, PipesOwnership, UpdateCustomProviderDefinition, ) @@ -69,9 +74,9 @@ def list_data_integrations( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. ownership: Only return Data Integrations with this ownership: `user` for the integrations users connect their own accounts to, or `organization` for the roots organizations connect to. Omit to return both. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -121,7 +126,7 @@ def create_data_integration( ) -> DataIntegration: """Create a data integration - Creates a data integration for a provider. Set `credentials.type` to `custom` to use your own OAuth app credentials or `organization` to have each organization supply its own. Set `auth_methods` to `["api_key"]` to create an API key integration; you may optionally supply an `api_key` block to install a first tenant in the same call. Set `auth_methods` to `["client_credentials"]` to create a client-credentials integration; client credentials are installed per-tenant afterwards. Set `ownership` to `organization` to create the integration organizations connect to instead of the default user-owned one; a provider may have one of each. For a built-in provider, pass its slug as `provider`. For a custom provider, pass a new slug plus a `custom_provider` definition, or the slug of an existing custom provider (without `custom_provider`) to add the other ownership. + Creates a data integration for a provider. Set `credentials.type` to `custom` to use your own OAuth app credentials or `organization` to have each organization supply its own. Set `auth_methods` to `[\"api_key\"]` to create an API key integration; you may optionally supply an `api_key` block to install a first tenant in the same call. Set `auth_methods` to `[\"client_credentials\"]` to create a client-credentials integration; client credentials are installed per-tenant afterwards. Set `ownership` to `organization` to create the integration organizations connect to instead of the default user-owned one; a provider may have one of each. For a built-in provider, pass its slug as `provider`. For a custom provider, pass a new slug plus a `custom_provider` definition, or the slug of an existing custom provider (without `custom_provider`) to add the other ownership. Args: provider: The provider to create a Data Integration for. For a built-in provider use its slug (e.g. `github`, `slack`). For a custom provider, this is the new provider slug and `custom_provider` must be supplied. A custom provider slug cannot shadow an existing global provider slug. @@ -129,9 +134,9 @@ def create_data_integration( description: An optional description of the Data Integration. enabled: Whether the Data Integration is enabled. Defaults to `false`. scopes: The OAuth scopes to request for the Data Integration. Defaults to the provider's configured scopes when omitted. - auth_methods: How accounts authenticate with the provider. Defaults to `["oauth"]`. Use `["api_key"]` to declare an API key integration; `credentials` is then not required and keys are supplied per-tenant (optionally via `api_key` on this request). Use `["client_credentials"]` to declare a client-credentials integration; `credentials` is likewise not required and client credentials are supplied per-tenant. + auth_methods: How accounts authenticate with the provider. Defaults to `[\"oauth\"]`. Use `[\"api_key\"]` to declare an API key integration; `credentials` is then not required and keys are supplied per-tenant (optionally via `api_key` on this request). Use `[\"client_credentials\"]` to declare a client-credentials integration; `credentials` is likewise not required and client credentials are supplied per-tenant. config: Provider-specific config values (e.g. a Snowflake `account`), keyed by the config field. Only fields the built-in provider declares are accepted. - credentials: The OAuth credentials to configure for the Data Integration. Required for OAuth integrations; omit when `auth_methods` is `["api_key"]`. + credentials: The OAuth credentials to configure for the Data Integration. Required for OAuth integrations; omit when `auth_methods` is `[\"api_key\"]`. api_key: An optional API key to install for the first tenant on an `api_key` integration. Omit to declare a keyless integration; tenants can be added later via the per-installation API key path. custom_provider: The OAuth definition for a custom provider. Supply this to define a custom provider; omit it to create an integration for a built-in provider. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -366,6 +371,9 @@ def authorize_data_integration( *, user_id: str, organization_id: str | None = None, + connection_owner: DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner + | str + | None = None, return_to: str | None = None, config: dict[str, str] | None = None, request_options: RequestOptions | None = None, @@ -376,8 +384,9 @@ def authorize_data_integration( Args: slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). - user_id: The ID of the user to authorize. - organization_id: An organization ID to scope the authorization to a specific organization. + user_id: The ID of the user to authorize. When `connection_owner` is `organization`, this is the user authorizing on behalf of the organization; they must be an active member of the organization and do not become the owner of the resulting connected account. + organization_id: An organization ID to scope the authorization to a specific organization. Required when `connection_owner` is `organization`. + connection_owner: Who will own the connected account. `user` (the default) connects the user's own account. `organization` connects the organization's shared account and requires `organization_id`. return_to: The URL to redirect the user to after authorization. config: Connect-time config values for the provider-declared `installation`-scope fields (e.g. a Zendesk `subdomain`), keyed by the config field. Only fields the provider declares may be supplied, and required fields must be provided unless already pinned on the integration. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -398,6 +407,9 @@ def authorize_data_integration( for k, v in { "user_id": user_id, "organization_id": organization_id, + "connection_owner": enum_value(connection_owner) + if connection_owner is not None + else None, "return_to": return_to, "config": config, }.items() @@ -1044,9 +1056,9 @@ async def list_data_integrations( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. ownership: Only return Data Integrations with this ownership: `user` for the integrations users connect their own accounts to, or `organization` for the roots organizations connect to. Omit to return both. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -1096,7 +1108,7 @@ async def create_data_integration( ) -> DataIntegration: """Create a data integration - Creates a data integration for a provider. Set `credentials.type` to `custom` to use your own OAuth app credentials or `organization` to have each organization supply its own. Set `auth_methods` to `["api_key"]` to create an API key integration; you may optionally supply an `api_key` block to install a first tenant in the same call. Set `auth_methods` to `["client_credentials"]` to create a client-credentials integration; client credentials are installed per-tenant afterwards. Set `ownership` to `organization` to create the integration organizations connect to instead of the default user-owned one; a provider may have one of each. For a built-in provider, pass its slug as `provider`. For a custom provider, pass a new slug plus a `custom_provider` definition, or the slug of an existing custom provider (without `custom_provider`) to add the other ownership. + Creates a data integration for a provider. Set `credentials.type` to `custom` to use your own OAuth app credentials or `organization` to have each organization supply its own. Set `auth_methods` to `[\"api_key\"]` to create an API key integration; you may optionally supply an `api_key` block to install a first tenant in the same call. Set `auth_methods` to `[\"client_credentials\"]` to create a client-credentials integration; client credentials are installed per-tenant afterwards. Set `ownership` to `organization` to create the integration organizations connect to instead of the default user-owned one; a provider may have one of each. For a built-in provider, pass its slug as `provider`. For a custom provider, pass a new slug plus a `custom_provider` definition, or the slug of an existing custom provider (without `custom_provider`) to add the other ownership. Args: provider: The provider to create a Data Integration for. For a built-in provider use its slug (e.g. `github`, `slack`). For a custom provider, this is the new provider slug and `custom_provider` must be supplied. A custom provider slug cannot shadow an existing global provider slug. @@ -1104,9 +1116,9 @@ async def create_data_integration( description: An optional description of the Data Integration. enabled: Whether the Data Integration is enabled. Defaults to `false`. scopes: The OAuth scopes to request for the Data Integration. Defaults to the provider's configured scopes when omitted. - auth_methods: How accounts authenticate with the provider. Defaults to `["oauth"]`. Use `["api_key"]` to declare an API key integration; `credentials` is then not required and keys are supplied per-tenant (optionally via `api_key` on this request). Use `["client_credentials"]` to declare a client-credentials integration; `credentials` is likewise not required and client credentials are supplied per-tenant. + auth_methods: How accounts authenticate with the provider. Defaults to `[\"oauth\"]`. Use `[\"api_key\"]` to declare an API key integration; `credentials` is then not required and keys are supplied per-tenant (optionally via `api_key` on this request). Use `[\"client_credentials\"]` to declare a client-credentials integration; `credentials` is likewise not required and client credentials are supplied per-tenant. config: Provider-specific config values (e.g. a Snowflake `account`), keyed by the config field. Only fields the built-in provider declares are accepted. - credentials: The OAuth credentials to configure for the Data Integration. Required for OAuth integrations; omit when `auth_methods` is `["api_key"]`. + credentials: The OAuth credentials to configure for the Data Integration. Required for OAuth integrations; omit when `auth_methods` is `[\"api_key\"]`. api_key: An optional API key to install for the first tenant on an `api_key` integration. Omit to declare a keyless integration; tenants can be added later via the per-installation API key path. custom_provider: The OAuth definition for a custom provider. Supply this to define a custom provider; omit it to create an integration for a built-in provider. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -1341,6 +1353,9 @@ async def authorize_data_integration( *, user_id: str, organization_id: str | None = None, + connection_owner: DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner + | str + | None = None, return_to: str | None = None, config: dict[str, str] | None = None, request_options: RequestOptions | None = None, @@ -1351,8 +1366,9 @@ async def authorize_data_integration( Args: slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). - user_id: The ID of the user to authorize. - organization_id: An organization ID to scope the authorization to a specific organization. + user_id: The ID of the user to authorize. When `connection_owner` is `organization`, this is the user authorizing on behalf of the organization; they must be an active member of the organization and do not become the owner of the resulting connected account. + organization_id: An organization ID to scope the authorization to a specific organization. Required when `connection_owner` is `organization`. + connection_owner: Who will own the connected account. `user` (the default) connects the user's own account. `organization` connects the organization's shared account and requires `organization_id`. return_to: The URL to redirect the user to after authorization. config: Connect-time config values for the provider-declared `installation`-scope fields (e.g. a Zendesk `subdomain`), keyed by the config field. Only fields the provider declares may be supplied, and required fields must be provided unless already pinned on the integration. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -1373,6 +1389,9 @@ async def authorize_data_integration( for k, v in { "user_id": user_id, "organization_id": organization_id, + "connection_owner": enum_value(connection_owner) + if connection_owner is not None + else None, "return_to": return_to, "config": config, }.items() diff --git a/src/workos/pipes/models/__init__.py b/src/workos/pipes/models/__init__.py index 1a6cff7ae..dd91230f6 100644 --- a/src/workos/pipes/models/__init__.py +++ b/src/workos/pipes/models/__init__.py @@ -3,7 +3,7 @@ from workos.common.models.connected_account import ConnectedAccount as ConnectedAccount from .api_key_installation import ApiKeyInstallation as ApiKeyInstallation -from .connected_account_input import ConnectedAccountInput as ConnectedAccountInput +from .connected_account_input import * from .create_data_integration import CreateDataIntegration as CreateDataIntegration from .custom_provider_definition import ( CustomProviderDefinition as CustomProviderDefinition, @@ -42,15 +42,9 @@ from .data_integrations_get_user_token_request import ( DataIntegrationsGetUserTokenRequest as DataIntegrationsGetUserTokenRequest, ) -from .data_integrations_list_response import ( - DataIntegrationsListResponse as DataIntegrationsListResponse, -) -from .data_integrations_list_response_data import ( - DataIntegrationsListResponseData as DataIntegrationsListResponseData, -) -from .data_integrations_list_response_data_connected_account import ( - DataIntegrationsListResponseDataConnectedAccount as DataIntegrationsListResponseDataConnectedAccount, -) +from .data_integrations_list_response import * +from .data_integrations_list_response_data import * +from .data_integrations_list_response_data_connected_account import * from .data_integrations_upsert_api_key_request import ( DataIntegrationsUpsertApiKeyRequest as DataIntegrationsUpsertApiKeyRequest, ) diff --git a/src/workos/pipes/models/create_data_integration.py b/src/workos/pipes/models/create_data_integration.py index 367096750..3a259a140 100644 --- a/src/workos/pipes/models/create_data_integration.py +++ b/src/workos/pipes/models/create_data_integration.py @@ -34,11 +34,11 @@ class CreateDataIntegration: scopes: list[str] | None = None """The OAuth scopes to request for the Data Integration. Defaults to the provider's configured scopes when omitted.""" auth_methods: list[CreateDataIntegrationAuthMethods] | None = None - """How accounts authenticate with the provider. Defaults to `["oauth"]`. Use `["api_key"]` to declare an API key integration; `credentials` is then not required and keys are supplied per-tenant (optionally via `api_key` on this request). Use `["client_credentials"]` to declare a client-credentials integration; `credentials` is likewise not required and client credentials are supplied per-tenant.""" + """How accounts authenticate with the provider. Defaults to `[\"oauth\"]`. Use `[\"api_key\"]` to declare an API key integration; `credentials` is then not required and keys are supplied per-tenant (optionally via `api_key` on this request). Use `[\"client_credentials\"]` to declare a client-credentials integration; `credentials` is likewise not required and client credentials are supplied per-tenant.""" config: dict[str, str] | None = None """Provider-specific config values (e.g. a Snowflake `account`), keyed by the config field. Only fields the built-in provider declares are accepted.""" credentials: DataIntegrationCredentialsInput | None = None - """The OAuth credentials to configure for the Data Integration. Required for OAuth integrations; omit when `auth_methods` is `["api_key"]`.""" + """The OAuth credentials to configure for the Data Integration. Required for OAuth integrations; omit when `auth_methods` is `[\"api_key\"]`.""" api_key: ApiKeyInstallation | None = None """An optional API key to install for the first tenant on an `api_key` integration. Omit to declare a keyless integration; tenants can be added later via the per-installation API key path.""" custom_provider: CustomProviderDefinition | None = None diff --git a/src/workos/pipes/models/data_integration_access_token_response.py b/src/workos/pipes/models/data_integration_access_token_response.py index 3d3caa4ec..ab1bc53d0 100644 --- a/src/workos/pipes/models/data_integration_access_token_response.py +++ b/src/workos/pipes/models/data_integration_access_token_response.py @@ -25,8 +25,8 @@ class DataIntegrationAccessTokenResponse: access_token: DataIntegrationAccessTokenResponseAccessToken | None = None """The [access token](https://workos.com/docs/reference/pipes/access-token) object, present when `active` is `true`.""" error: DataIntegrationAccessTokenResponseError | None = None - """- `"not_installed"`: The user does not have the integration installed. -- `"needs_reauthorization"`: The user needs to reauthorize the integration.""" + """- `\"not_installed\"`: The user does not have the integration installed. +- `\"needs_reauthorization\"`: The user needs to reauthorize the integration.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> DataIntegrationAccessTokenResponse: diff --git a/src/workos/pipes/models/data_integration_credentials_response.py b/src/workos/pipes/models/data_integration_credentials_response.py index 6c499bbab..54f33d1e4 100644 --- a/src/workos/pipes/models/data_integration_credentials_response.py +++ b/src/workos/pipes/models/data_integration_credentials_response.py @@ -26,8 +26,8 @@ class DataIntegrationCredentialsResponse: """The credential object containing the vended secret.""" error: DataIntegrationCredentialsResponseError | None = None """The reason credentials are unavailable. Additional values may be added in the future; handle unknown values gracefully. -- `"not_installed"`: The user does not have the integration installed. -- `"needs_reauthorization"`: The user needs to reauthorize the integration.""" +- `\"not_installed\"`: The user does not have the integration installed. +- `\"needs_reauthorization\"`: The user needs to reauthorize the integration.""" @classmethod def from_dict(cls, data: dict[str, Any]) -> DataIntegrationCredentialsResponse: diff --git a/src/workos/pipes/models/data_integration_installation.py b/src/workos/pipes/models/data_integration_installation.py index 983f90365..d5a3e7baa 100644 --- a/src/workos/pipes/models/data_integration_installation.py +++ b/src/workos/pipes/models/data_integration_installation.py @@ -3,9 +3,13 @@ from __future__ import annotations from dataclasses import dataclass +from enum import Enum from typing import Any from workos._types import _raise_deserialize_error +from workos.common.models.data_integration_installation_connection_role import ( + DataIntegrationInstallationConnectionRole, +) @dataclass(slots=True) @@ -14,6 +18,12 @@ class DataIntegrationInstallation: id: str """Unique identifier of the installation.""" + connection_role: DataIntegrationInstallationConnectionRole + """Whether this is the compatibility connection visible to undeclared clients or a standard connection for plural-aware clients.""" + account_identifier: str | None + """A best-effort provider account identifier used for correlation, not connection selection.""" + account_display_name: str | None + """A mutable, non-unique display name for this connection.""" user_id: str | None """The User the API key was installed for. Null on an `organization`-owned integration, whose installations belong to the organization.""" organization_id: str | None @@ -27,6 +37,11 @@ def from_dict(cls, data: dict[str, Any]) -> DataIntegrationInstallation: try: return cls( id=data["id"], + connection_role=DataIntegrationInstallationConnectionRole( + data["connection_role"] + ), + account_identifier=data["account_identifier"], + account_display_name=data["account_display_name"], user_id=data["user_id"], organization_id=data["organization_id"], api_key_last_4=data["api_key_last_4"], @@ -38,6 +53,19 @@ def to_dict(self) -> dict[str, Any]: """Serialize to a dictionary.""" result: dict[str, Any] = {} result["id"] = self.id + result["connection_role"] = ( + self.connection_role.value + if isinstance(self.connection_role, Enum) + else self.connection_role + ) + if self.account_identifier is not None: + result["account_identifier"] = self.account_identifier + else: + result["account_identifier"] = None + if self.account_display_name is not None: + result["account_display_name"] = self.account_display_name + else: + result["account_display_name"] = None if self.user_id is not None: result["user_id"] = self.user_id else: diff --git a/src/workos/pipes/models/data_integrations_get_data_integration_authorize_url_request.py b/src/workos/pipes/models/data_integrations_get_data_integration_authorize_url_request.py index 6dca8f1ed..1ca8b5d5e 100644 --- a/src/workos/pipes/models/data_integrations_get_data_integration_authorize_url_request.py +++ b/src/workos/pipes/models/data_integrations_get_data_integration_authorize_url_request.py @@ -3,9 +3,13 @@ from __future__ import annotations from dataclasses import dataclass +from enum import Enum from typing import Any from workos._types import _raise_deserialize_error +from workos.common.models.data_integrations_get_data_integration_authorize_url_request_connection_owner import ( + DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner, +) @dataclass(slots=True) @@ -13,9 +17,13 @@ class DataIntegrationsGetDataIntegrationAuthorizeUrlRequest: """Data Integrations Get Data Integration Authorize Url Request model.""" user_id: str - """The ID of the user to authorize.""" + """The ID of the user to authorize. When `connection_owner` is `organization`, this is the user authorizing on behalf of the organization; they must be an active member of the organization and do not become the owner of the resulting connected account.""" organization_id: str | None = None - """An organization ID to scope the authorization to a specific organization.""" + """An organization ID to scope the authorization to a specific organization. Required when `connection_owner` is `organization`.""" + connection_owner: ( + DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner | None + ) = None + """Who will own the connected account. `user` (the default) connects the user's own account. `organization` connects the organization's shared account and requires `organization_id`.""" return_to: str | None = None """The URL to redirect the user to after authorization.""" config: dict[str, str] | None = None @@ -30,6 +38,11 @@ def from_dict( return cls( user_id=data["user_id"], organization_id=data.get("organization_id"), + connection_owner=DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner( + _v_connection_owner + ) + if (_v_connection_owner := data.get("connection_owner")) is not None + else None, return_to=data.get("return_to"), config=data.get("config"), ) @@ -44,6 +57,12 @@ def to_dict(self) -> dict[str, Any]: result["user_id"] = self.user_id if self.organization_id is not None: result["organization_id"] = self.organization_id + if self.connection_owner is not None: + result["connection_owner"] = ( + self.connection_owner.value + if isinstance(self.connection_owner, Enum) + else self.connection_owner + ) if self.return_to is not None: result["return_to"] = self.return_to if self.config is not None: diff --git a/src/workos/radar/_resource.py b/src/workos/radar/_resource.py index e62271956..c244c45bb 100644 --- a/src/workos/radar/_resource.py +++ b/src/workos/radar/_resource.py @@ -96,8 +96,8 @@ def update_attempt( Args: id: The unique identifier of the Radar attempt to update. - challenge_status: Set to `"success"` to mark the challenge as completed. - attempt_status: Set to `"success"` to mark the authentication attempt as successful. + challenge_status: Set to `\"success\"` to mark the challenge as completed. + attempt_status: Set to `\"success\"` to mark the authentication attempt as successful. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Raises: @@ -269,8 +269,8 @@ async def update_attempt( Args: id: The unique identifier of the Radar attempt to update. - challenge_status: Set to `"success"` to mark the challenge as completed. - attempt_status: Set to `"success"` to mark the authentication attempt as successful. + challenge_status: Set to `\"success\"` to mark the challenge as completed. + attempt_status: Set to `\"success\"` to mark the authentication attempt as successful. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Raises: diff --git a/src/workos/radar/models/radar_standalone_update_radar_attempt_request.py b/src/workos/radar/models/radar_standalone_update_radar_attempt_request.py index 9a4033129..fc6aee15f 100644 --- a/src/workos/radar/models/radar_standalone_update_radar_attempt_request.py +++ b/src/workos/radar/models/radar_standalone_update_radar_attempt_request.py @@ -13,9 +13,9 @@ class RadarStandaloneUpdateRadarAttemptRequest: """Radar Standalone Update Radar Attempt Request model.""" challenge_status: Literal["success"] | None = None - """Set to `"success"` to mark the challenge as completed.""" + """Set to `\"success\"` to mark the challenge as completed.""" attempt_status: Literal["success"] | None = None - """Set to `"success"` to mark the authentication attempt as successful.""" + """Set to `\"success\"` to mark the authentication attempt as successful.""" @classmethod def from_dict( diff --git a/src/workos/sso/_resource.py b/src/workos/sso/_resource.py index 224c3c02b..accfa86ec 100644 --- a/src/workos/sso/_resource.py +++ b/src/workos/sso/_resource.py @@ -656,7 +656,7 @@ def get_authorization_url( domain_hint: Can be used to pre-fill the domain field when initiating authentication with Microsoft OAuth or with a Google SAML connection type. login_hint: Can be used to pre-fill the username/email address field of the IdP sign-in page for the user, if you know their username ahead of time. Currently supported for OAuth, OpenID Connect, Okta, Entra ID, and custom SAML connections. nonce: A random string generated by the client that is used to mitigate replay attacks. - prompt: If set to `login`, forces re-authentication at the identity provider. For supported SAML providers this sets `ForceAuthn="true"` in the SAML request; providers that don't support it are unaffected. + prompt: If set to `login`, forces re-authentication at the identity provider. For supported SAML providers this sets `ForceAuthn=\"true\"` in the SAML request; providers that don't support it are unaffected. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -1524,7 +1524,7 @@ def get_authorization_url( domain_hint: Can be used to pre-fill the domain field when initiating authentication with Microsoft OAuth or with a Google SAML connection type. login_hint: Can be used to pre-fill the username/email address field of the IdP sign-in page for the user, if you know their username ahead of time. Currently supported for OAuth, OpenID Connect, Okta, Entra ID, and custom SAML connections. nonce: A random string generated by the client that is used to mitigate replay attacks. - prompt: If set to `login`, forces re-authentication at the identity provider. For supported SAML providers this sets `ForceAuthn="true"` in the SAML request; providers that don't support it are unaffected. + prompt: If set to `login`, forces re-authentication at the identity provider. For supported SAML providers this sets `ForceAuthn=\"true\"` in the SAML request; providers that don't support it are unaffected. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: diff --git a/src/workos/types/organizations_data_providers/__init__.py b/src/workos/types/organizations_data_providers/__init__.py new file mode 100644 index 000000000..cd43f694a --- /dev/null +++ b/src/workos/types/organizations_data_providers/__init__.py @@ -0,0 +1,3 @@ +# This file is auto-generated by oagen. Do not edit. + +from workos.organizations_data_providers.models import * diff --git a/src/workos/user_management/_resource.py b/src/workos/user_management/_resource.py index 9bb150c8d..269df8703 100644 --- a/src/workos/user_management/_resource.py +++ b/src/workos/user_management/_resource.py @@ -581,7 +581,7 @@ def get_authorization_url( Generates an OAuth 2.0 authorization URL to authenticate a user with AuthKit or SSO. Args: - code_challenge_method: The only valid PKCE code challenge method is `"S256"`. Required when specifying a `code_challenge`. + code_challenge_method: The only valid PKCE code challenge method is `\"S256\"`. Required when specifying a `code_challenge`. code_challenge: Code challenge derived from the code verifier used for the PKCE flow. domain_hint: A domain hint for SSO connection lookup. connection_id: The ID of an SSO connection to use for authentication. @@ -832,9 +832,9 @@ def list_authkit_oauth_resources( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -875,7 +875,7 @@ def create_authkit_oauth_resource( Adds an MCP resource indicator (RFC 8707) to an environment, leaving any others in place. Args: - uri: The resource URI. May be a wildcard pattern with a single `*` in the leftmost hostname label, where enabled for the environment. + uri: The resource URI. May be a wildcard pattern with a single `*`, either in the leftmost hostname label or as the final path segment, where enabled for the environment. default: Whether the resource being created becomes the environment default, clearing any previous default. Applies at creation only — this API has no update endpoint yet, so changing the default on an existing resource is done from the dashboard. A wildcard pattern cannot be the default. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -945,9 +945,9 @@ def list_cors_origins( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -1165,9 +1165,9 @@ def list_users( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. organization: (deprecated) Filter users by the organization they are a member of. Deprecated in favor of `organization_id`. organization_id: Filter users by the organization they are a member of. email: Filter users by their email address. @@ -1648,9 +1648,9 @@ def list_sessions( Args: id: The ID of the user. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -1698,9 +1698,9 @@ def list_invitations( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. organization_id: The ID of the [organization](https://workos.com/docs/reference/organization) that the recipient will join. email: The email address of the recipient. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -2110,9 +2110,9 @@ def list_redirect_uris( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -2220,9 +2220,9 @@ def list_user_authorized_applications( Args: user_id: The ID of the user. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -2474,9 +2474,9 @@ def list_waitlist_entries( Args: id: The unique ID of the waitlist, or the literal `default` for the environment's default waitlist. The default waitlist is created when its first entry is added, so read requests for `default` return a `404` until then. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. state: Filter waitlist entries by their state. email: Filter waitlist entries by their exact email address. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -3333,7 +3333,7 @@ def get_authorization_url( Generates an OAuth 2.0 authorization URL to authenticate a user with AuthKit or SSO. Args: - code_challenge_method: The only valid PKCE code challenge method is `"S256"`. Required when specifying a `code_challenge`. + code_challenge_method: The only valid PKCE code challenge method is `\"S256\"`. Required when specifying a `code_challenge`. code_challenge: Code challenge derived from the code verifier used for the PKCE flow. domain_hint: A domain hint for SSO connection lookup. connection_id: The ID of an SSO connection to use for authentication. @@ -3584,9 +3584,9 @@ async def list_authkit_oauth_resources( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -3627,7 +3627,7 @@ async def create_authkit_oauth_resource( Adds an MCP resource indicator (RFC 8707) to an environment, leaving any others in place. Args: - uri: The resource URI. May be a wildcard pattern with a single `*` in the leftmost hostname label, where enabled for the environment. + uri: The resource URI. May be a wildcard pattern with a single `*`, either in the leftmost hostname label or as the final path segment, where enabled for the environment. default: Whether the resource being created becomes the environment default, clearing any previous default. Applies at creation only — this API has no update endpoint yet, so changing the default on an existing resource is done from the dashboard. A wildcard pattern cannot be the default. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -3697,9 +3697,9 @@ async def list_cors_origins( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -3917,9 +3917,9 @@ async def list_users( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. organization: (deprecated) Filter users by the organization they are a member of. Deprecated in favor of `organization_id`. organization_id: Filter users by the organization they are a member of. email: Filter users by their email address. @@ -4400,9 +4400,9 @@ async def list_sessions( Args: id: The ID of the user. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -4450,9 +4450,9 @@ async def list_invitations( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. organization_id: The ID of the [organization](https://workos.com/docs/reference/organization) that the recipient will join. email: The email address of the recipient. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. @@ -4862,9 +4862,9 @@ async def list_redirect_uris( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -4972,9 +4972,9 @@ async def list_user_authorized_applications( Args: user_id: The ID of the user. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -5226,9 +5226,9 @@ async def list_waitlist_entries( Args: id: The unique ID of the waitlist, or the literal `default` for the environment's default waitlist. The default waitlist is created when its first entry is added, so read requests for `default` return a `404` until then. limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. state: Filter waitlist entries by their state. email: Filter waitlist entries by their exact email address. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. diff --git a/src/workos/user_management/models/create_authkit_oauth_resource.py b/src/workos/user_management/models/create_authkit_oauth_resource.py index 2db9b4bcf..e312b0369 100644 --- a/src/workos/user_management/models/create_authkit_oauth_resource.py +++ b/src/workos/user_management/models/create_authkit_oauth_resource.py @@ -13,7 +13,7 @@ class CreateAuthkitOAuthResource: """Create Authkit O Auth Resource model.""" uri: str - """The resource URI. May be a wildcard pattern with a single `*` in the leftmost hostname label, where enabled for the environment.""" + """The resource URI. May be a wildcard pattern with a single `*`, either in the leftmost hostname label or as the final path segment, where enabled for the environment.""" default: bool | None = None """Whether the resource being created becomes the environment default, clearing any previous default. Applies at creation only — this API has no update endpoint yet, so changing the default on an existing resource is done from the dashboard. A wildcard pattern cannot be the default.""" diff --git a/src/workos/webhooks/_resource.py b/src/workos/webhooks/_resource.py index 9f31e50ff..94d813971 100644 --- a/src/workos/webhooks/_resource.py +++ b/src/workos/webhooks/_resource.py @@ -54,9 +54,9 @@ def list_webhook_endpoints( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: @@ -311,9 +311,9 @@ async def list_webhook_endpoints( Args: limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. - before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. - after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. - order: Order the results by the creation time. Supported values are `"asc"` (ascending), `"desc"` (descending), and `"normal"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `before=\"obj_123\"` to fetch a new batch of objects before `\"obj_123\"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `\"obj_123\"`, your subsequent call can include `after=\"obj_123\"` to fetch a new batch of objects after `\"obj_123\"`. + order: Order the results by the creation time. Supported values are `\"asc\"` (ascending), `\"desc\"` (descending), and `\"normal\"` (descending with reversed cursor semantics where `before` fetches older records and `after` fetches newer records). Defaults to `desc`. request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. Returns: diff --git a/tests/fixtures/connected_account.json b/tests/fixtures/connected_account.json index a863395dc..c98e41871 100644 --- a/tests/fixtures/connected_account.json +++ b/tests/fixtures/connected_account.json @@ -1,6 +1,9 @@ { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": null, "scopes": [ diff --git a/tests/fixtures/data_integration_installation.json b/tests/fixtures/data_integration_installation.json index 394d135d7..820a93446 100644 --- a/tests/fixtures/data_integration_installation.json +++ b/tests/fixtures/data_integration_installation.json @@ -1,5 +1,8 @@ { "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": null, "api_key_last_4": "cdef" diff --git a/tests/fixtures/data_integrations_get_data_integration_authorize_url_request.json b/tests/fixtures/data_integrations_get_data_integration_authorize_url_request.json index 2d0e5bb20..4a7ea84c3 100644 --- a/tests/fixtures/data_integrations_get_data_integration_authorize_url_request.json +++ b/tests/fixtures/data_integrations_get_data_integration_authorize_url_request.json @@ -1,6 +1,7 @@ { "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_owner": "user", "return_to": "https://example.com/callback", "config": { "subdomain": "acme" diff --git a/tests/fixtures/data_integrations_list_response.json b/tests/fixtures/data_integrations_list_response.json index 365f599fc..6c46a8188 100644 --- a/tests/fixtures/data_integrations_list_response.json +++ b/tests/fixtures/data_integrations_list_response.json @@ -16,12 +16,16 @@ "auth_methods": [ "oauth" ], + "connection_owner": "user", "ownership": "userland_user", "created_at": "2024-01-15T10:30:00.000Z", "updated_at": "2024-01-15T10:30:00.000Z", "connected_account": { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": null, "scopes": [ @@ -44,6 +48,9 @@ { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": null, "scopes": [ diff --git a/tests/fixtures/data_integrations_list_response_data.json b/tests/fixtures/data_integrations_list_response_data.json index f3997ed18..582a3a34b 100644 --- a/tests/fixtures/data_integrations_list_response_data.json +++ b/tests/fixtures/data_integrations_list_response_data.json @@ -13,12 +13,16 @@ "auth_methods": [ "oauth" ], + "connection_owner": "user", "ownership": "userland_user", "created_at": "2024-01-15T10:30:00.000Z", "updated_at": "2024-01-15T10:30:00.000Z", "connected_account": { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": null, "scopes": [ @@ -41,6 +45,9 @@ { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": null, "scopes": [ diff --git a/tests/fixtures/data_integrations_list_response_data_connected_account.json b/tests/fixtures/data_integrations_list_response_data_connected_account.json index 467bb97e7..a6a8baf44 100644 --- a/tests/fixtures/data_integrations_list_response_data_connected_account.json +++ b/tests/fixtures/data_integrations_list_response_data_connected_account.json @@ -1,6 +1,9 @@ { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": null, "scopes": [ diff --git a/tests/fixtures/pipe_connected_account.json b/tests/fixtures/pipe_connected_account.json index cb4744985..0a016bdaa 100644 --- a/tests/fixtures/pipe_connected_account.json +++ b/tests/fixtures/pipe_connected_account.json @@ -1,6 +1,9 @@ { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", @@ -9,6 +12,8 @@ "repo", "user:email" ], + "auth_method": "oauth", + "api_key_last_4": null, "state": "connected", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z" diff --git a/tests/fixtures/pipes_account_connection_add_failed.json b/tests/fixtures/pipes_account_connection_add_failed.json new file mode 100644 index 000000000..085d72283 --- /dev/null +++ b/tests/fixtures/pipes_account_connection_add_failed.json @@ -0,0 +1,39 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.add_failed", + "data": { + "object": "connection_failed", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "account_identifier": null, + "error_code": "authorization_code_exchange_error", + "error_reason": "The authorization code has expired.", + "provider_error": "access_denied", + "provider_error_description": "The user denied the authorization request.", + "created_at": "2026-01-15T12:00:00.000Z" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/pipes_account_connection_add_failed_data.json b/tests/fixtures/pipes_account_connection_add_failed_data.json new file mode 100644 index 000000000..5a44d8e23 --- /dev/null +++ b/tests/fixtures/pipes_account_connection_add_failed_data.json @@ -0,0 +1,13 @@ +{ + "object": "connection_failed", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "account_identifier": null, + "error_code": "authorization_code_exchange_error", + "error_reason": "The authorization code has expired.", + "provider_error": "access_denied", + "provider_error_description": "The user denied the authorization request.", + "created_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/pipes_account_connection_connected.json b/tests/fixtures/pipes_account_connection_connected.json new file mode 100644 index 000000000..bd1caa7a9 --- /dev/null +++ b/tests/fixtures/pipes_account_connection_connected.json @@ -0,0 +1,46 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.connected", + "data": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "scopes": [ + "repo", + "user:email" + ], + "auth_method": "oauth", + "api_key_last_4": null, + "state": "connected", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/pipes_account_connection_connection_failed.json b/tests/fixtures/pipes_account_connection_connection_failed.json new file mode 100644 index 000000000..6551445c5 --- /dev/null +++ b/tests/fixtures/pipes_account_connection_connection_failed.json @@ -0,0 +1,39 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.connection_failed", + "data": { + "object": "connection_failed", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "account_identifier": null, + "error_code": "authorization_code_exchange_error", + "error_reason": "The authorization code has expired.", + "provider_error": "access_denied", + "provider_error_description": "The user denied the authorization request.", + "created_at": "2026-01-15T12:00:00.000Z" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/pipes_account_connection_connection_failed_data.json b/tests/fixtures/pipes_account_connection_connection_failed_data.json new file mode 100644 index 000000000..5a44d8e23 --- /dev/null +++ b/tests/fixtures/pipes_account_connection_connection_failed_data.json @@ -0,0 +1,13 @@ +{ + "object": "connection_failed", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "account_identifier": null, + "error_code": "authorization_code_exchange_error", + "error_reason": "The authorization code has expired.", + "provider_error": "access_denied", + "provider_error_description": "The user denied the authorization request.", + "created_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/pipes_account_connection_disconnected.json b/tests/fixtures/pipes_account_connection_disconnected.json new file mode 100644 index 000000000..069add214 --- /dev/null +++ b/tests/fixtures/pipes_account_connection_disconnected.json @@ -0,0 +1,46 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.disconnected", + "data": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "scopes": [ + "repo", + "user:email" + ], + "auth_method": "oauth", + "api_key_last_4": null, + "state": "connected", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/pipes_account_connection_reauthorization_needed.json b/tests/fixtures/pipes_account_connection_reauthorization_needed.json new file mode 100644 index 000000000..909f7965c --- /dev/null +++ b/tests/fixtures/pipes_account_connection_reauthorization_needed.json @@ -0,0 +1,46 @@ +{ + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.reauthorization_needed", + "data": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "scopes": [ + "repo", + "user:email" + ], + "auth_method": "oauth", + "api_key_last_4": null, + "state": "connected", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "context": { + "google_analytics_client_id": "GA1.2.1234567890.1234567890", + "google_analytics_sessions": [ + { + "containerId": "GTM-ABCDEF", + "sessionId": "1234567890", + "sessionNumber": "1" + } + ], + "ajs_anonymous_id": "ajs_anon_01EHWNCE74X7JSDV0X3SZ3KJNY", + "client_id": "client_01EHWNCE74X7JSDV0X3SZ3KJNY", + "actor": { + "id": "user_01EHWNCE74X7JSDV0X3SZ3KJNY", + "source": "api", + "name": "Jane Doe" + }, + "previous_attributes": { + "key": {} + } + } +} diff --git a/tests/fixtures/pipes_connected_account_connected.json b/tests/fixtures/pipes_connected_account_connected.json index c72f06a2e..a4f73740e 100644 --- a/tests/fixtures/pipes_connected_account_connected.json +++ b/tests/fixtures/pipes_connected_account_connected.json @@ -5,6 +5,9 @@ "data": { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", @@ -13,6 +16,8 @@ "repo", "user:email" ], + "auth_method": "oauth", + "api_key_last_4": null, "state": "connected", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z" diff --git a/tests/fixtures/pipes_connected_account_connection_failed.json b/tests/fixtures/pipes_connected_account_connection_failed.json index db36784b9..41d77d1cc 100644 --- a/tests/fixtures/pipes_connected_account_connection_failed.json +++ b/tests/fixtures/pipes_connected_account_connection_failed.json @@ -8,6 +8,7 @@ "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "account_identifier": null, "error_code": "authorization_code_exchange_error", "error_reason": "The authorization code has expired.", "provider_error": "access_denied", diff --git a/tests/fixtures/pipes_connected_account_connection_failed_data.json b/tests/fixtures/pipes_connected_account_connection_failed_data.json index cd5f1fc65..5a44d8e23 100644 --- a/tests/fixtures/pipes_connected_account_connection_failed_data.json +++ b/tests/fixtures/pipes_connected_account_connection_failed_data.json @@ -4,6 +4,7 @@ "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "account_identifier": null, "error_code": "authorization_code_exchange_error", "error_reason": "The authorization code has expired.", "provider_error": "access_denied", diff --git a/tests/fixtures/pipes_connected_account_disconnected.json b/tests/fixtures/pipes_connected_account_disconnected.json index e66fab99f..46eb72791 100644 --- a/tests/fixtures/pipes_connected_account_disconnected.json +++ b/tests/fixtures/pipes_connected_account_disconnected.json @@ -5,6 +5,9 @@ "data": { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", @@ -13,6 +16,8 @@ "repo", "user:email" ], + "auth_method": "oauth", + "api_key_last_4": null, "state": "connected", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z" diff --git a/tests/fixtures/pipes_connected_account_reauthorization_needed.json b/tests/fixtures/pipes_connected_account_reauthorization_needed.json index 2aa234712..1afd4a7a5 100644 --- a/tests/fixtures/pipes_connected_account_reauthorization_needed.json +++ b/tests/fixtures/pipes_connected_account_reauthorization_needed.json @@ -5,6 +5,9 @@ "data": { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", @@ -13,6 +16,8 @@ "repo", "user:email" ], + "auth_method": "oauth", + "api_key_last_4": null, "state": "connected", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z" diff --git a/tests/test_common_models_round_trip.py b/tests/test_common_models_round_trip.py index 8498f807a..21438f6b6 100644 --- a/tests/test_common_models_round_trip.py +++ b/tests/test_common_models_round_trip.py @@ -248,6 +248,13 @@ PermissionUpdated, PermissionUpdatedData, PipeConnectedAccount, + PipesAccountConnectionAddFailed, + PipesAccountConnectionAddFailedData, + PipesAccountConnectionConnected, + PipesAccountConnectionConnectionFailed, + PipesAccountConnectionConnectionFailedData, + PipesAccountConnectionDisconnected, + PipesAccountConnectionReauthorizationNeeded, PipesConnectedAccountConnected, PipesConnectedAccountConnectionFailed, PipesConnectedAccountConnectionFailedData, @@ -702,34 +709,68 @@ def test_pipe_connected_account_minimal_payload(self): assert serialized["created_at"] == data["created_at"] assert serialized["updated_at"] == data["updated_at"] + def test_pipe_connected_account_omits_absent_optional_non_nullable_fields(self): + data = { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "scopes": ["repo", "user:email"], + "api_key_last_4": None, + "state": "connected", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = PipeConnectedAccount.from_dict(data) + serialized = instance.to_dict() + assert "connection_role" not in serialized + assert "auth_method" not in serialized + def test_pipe_connected_account_preserves_nullable_fields(self): data = { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": None, + "account_display_name": None, "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "provider_slug": "github", "user_id": None, "organization_id": None, "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, "state": "connected", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", } instance = PipeConnectedAccount.from_dict(data) serialized = instance.to_dict() + assert serialized["account_identifier"] is None + assert serialized["account_display_name"] is None assert serialized["user_id"] is None assert serialized["organization_id"] is None + assert serialized["api_key_last_4"] is None def test_pipe_connected_account_round_trips_unknown_enum_values(self): data = { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "unexpected_pipe_connected_account_connection_role", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", "scopes": ["repo", "user:email"], - "state": "unexpected_pipe_connected_account_state", + "auth_method": "oauth", + "api_key_last_4": None, + "state": "connected", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", } @@ -12333,6 +12374,462 @@ def test_permission_updated_data_preserves_nullable_fields(self): serialized = instance.to_dict() assert serialized["description"] is None + def test_pipes_account_connection_add_failed_round_trip(self): + data = load_fixture("pipes_account_connection_add_failed.json") + instance = PipesAccountConnectionAddFailed.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = PipesAccountConnectionAddFailed.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_pipes_account_connection_add_failed_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.add_failed", + "data": { + "object": "connection_failed", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "account_identifier": None, + "error_code": "authorization_code_exchange_error", + "error_reason": "The authorization code has expired.", + "provider_error": "access_denied", + "provider_error_description": "The user denied the authorization request.", + "created_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionAddFailed.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_pipes_account_connection_add_failed_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.add_failed", + "data": { + "object": "connection_failed", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "account_identifier": None, + "error_code": "authorization_code_exchange_error", + "error_reason": "The authorization code has expired.", + "provider_error": "access_denied", + "provider_error_description": "The user denied the authorization request.", + "created_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionAddFailed.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_pipes_account_connection_add_failed_data_round_trip(self): + data = load_fixture("pipes_account_connection_add_failed_data.json") + instance = PipesAccountConnectionAddFailedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = PipesAccountConnectionAddFailedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_pipes_account_connection_add_failed_data_minimal_payload(self): + data = { + "object": "connection_failed", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": None, + "organization_id": None, + "error_code": "authorization_code_exchange_error", + "error_reason": None, + "provider_error": None, + "provider_error_description": None, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionAddFailedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["data_integration_id"] == data["data_integration_id"] + assert serialized["provider_slug"] == data["provider_slug"] + assert serialized["user_id"] == data["user_id"] + assert serialized["organization_id"] == data["organization_id"] + assert serialized["error_code"] == data["error_code"] + assert serialized["error_reason"] == data["error_reason"] + assert serialized["provider_error"] == data["provider_error"] + assert ( + serialized["provider_error_description"] + == data["provider_error_description"] + ) + assert serialized["created_at"] == data["created_at"] + + def test_pipes_account_connection_add_failed_data_preserves_nullable_fields(self): + data = { + "object": "connection_failed", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": None, + "organization_id": None, + "account_identifier": None, + "error_code": "authorization_code_exchange_error", + "error_reason": None, + "provider_error": None, + "provider_error_description": None, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionAddFailedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["user_id"] is None + assert serialized["organization_id"] is None + assert serialized["account_identifier"] is None + assert serialized["error_reason"] is None + assert serialized["provider_error"] is None + assert serialized["provider_error_description"] is None + + def test_pipes_account_connection_connected_round_trip(self): + data = load_fixture("pipes_account_connection_connected.json") + instance = PipesAccountConnectionConnected.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = PipesAccountConnectionConnected.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_pipes_account_connection_connected_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.connected", + "data": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "state": "connected", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionConnected.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_pipes_account_connection_connected_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.connected", + "data": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "state": "connected", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionConnected.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_pipes_account_connection_connection_failed_round_trip(self): + data = load_fixture("pipes_account_connection_connection_failed.json") + instance = PipesAccountConnectionConnectionFailed.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = PipesAccountConnectionConnectionFailed.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_pipes_account_connection_connection_failed_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.connection_failed", + "data": { + "object": "connection_failed", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "account_identifier": None, + "error_code": "authorization_code_exchange_error", + "error_reason": "The authorization code has expired.", + "provider_error": "access_denied", + "provider_error_description": "The user denied the authorization request.", + "created_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionConnectionFailed.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_pipes_account_connection_connection_failed_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.connection_failed", + "data": { + "object": "connection_failed", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "account_identifier": None, + "error_code": "authorization_code_exchange_error", + "error_reason": "The authorization code has expired.", + "provider_error": "access_denied", + "provider_error_description": "The user denied the authorization request.", + "created_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionConnectionFailed.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_pipes_account_connection_connection_failed_data_round_trip(self): + data = load_fixture("pipes_account_connection_connection_failed_data.json") + instance = PipesAccountConnectionConnectionFailedData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = PipesAccountConnectionConnectionFailedData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_pipes_account_connection_connection_failed_data_minimal_payload(self): + data = { + "object": "connection_failed", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": None, + "organization_id": None, + "error_code": "authorization_code_exchange_error", + "error_reason": None, + "provider_error": None, + "provider_error_description": None, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionConnectionFailedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["data_integration_id"] == data["data_integration_id"] + assert serialized["provider_slug"] == data["provider_slug"] + assert serialized["user_id"] == data["user_id"] + assert serialized["organization_id"] == data["organization_id"] + assert serialized["error_code"] == data["error_code"] + assert serialized["error_reason"] == data["error_reason"] + assert serialized["provider_error"] == data["provider_error"] + assert ( + serialized["provider_error_description"] + == data["provider_error_description"] + ) + assert serialized["created_at"] == data["created_at"] + + def test_pipes_account_connection_connection_failed_data_preserves_nullable_fields( + self, + ): + data = { + "object": "connection_failed", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": None, + "organization_id": None, + "account_identifier": None, + "error_code": "authorization_code_exchange_error", + "error_reason": None, + "provider_error": None, + "provider_error_description": None, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionConnectionFailedData.from_dict(data) + serialized = instance.to_dict() + assert serialized["user_id"] is None + assert serialized["organization_id"] is None + assert serialized["account_identifier"] is None + assert serialized["error_reason"] is None + assert serialized["provider_error"] is None + assert serialized["provider_error_description"] is None + + def test_pipes_account_connection_disconnected_round_trip(self): + data = load_fixture("pipes_account_connection_disconnected.json") + instance = PipesAccountConnectionDisconnected.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = PipesAccountConnectionDisconnected.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_pipes_account_connection_disconnected_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.disconnected", + "data": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "state": "connected", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionDisconnected.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_pipes_account_connection_disconnected_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.disconnected", + "data": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "state": "connected", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionDisconnected.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + + def test_pipes_account_connection_reauthorization_needed_round_trip(self): + data = load_fixture("pipes_account_connection_reauthorization_needed.json") + instance = PipesAccountConnectionReauthorizationNeeded.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = PipesAccountConnectionReauthorizationNeeded.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_pipes_account_connection_reauthorization_needed_minimal_payload(self): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.reauthorization_needed", + "data": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "state": "connected", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionReauthorizationNeeded.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["event"] == data["event"] + assert serialized["data"] == data["data"] + assert serialized["created_at"] == data["created_at"] + + def test_pipes_account_connection_reauthorization_needed_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "object": "event", + "id": "event_01EHZNVPK3SFK441A1RGBFSHRT", + "event": "pipes.account_connection.reauthorization_needed", + "data": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "provider_slug": "github", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "state": "connected", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + }, + "created_at": "2026-01-15T12:00:00.000Z", + } + instance = PipesAccountConnectionReauthorizationNeeded.from_dict(data) + serialized = instance.to_dict() + assert "context" not in serialized + def test_pipes_connected_account_connected_round_trip(self): data = load_fixture("pipes_connected_account_connected.json") instance = PipesConnectedAccountConnected.from_dict(data) @@ -12349,11 +12846,16 @@ def test_pipes_connected_account_connected_minimal_payload(self): "data": { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, "state": "connected", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", @@ -12378,11 +12880,16 @@ def test_pipes_connected_account_connected_omits_absent_optional_non_nullable_fi "data": { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, "state": "connected", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", @@ -12412,6 +12919,7 @@ def test_pipes_connected_account_connection_failed_minimal_payload(self): "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "account_identifier": None, "error_code": "authorization_code_exchange_error", "error_reason": "The authorization code has expired.", "provider_error": "access_denied", @@ -12441,6 +12949,7 @@ def test_pipes_connected_account_connection_failed_omits_absent_optional_non_nul "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "account_identifier": None, "error_code": "authorization_code_exchange_error", "error_reason": "The authorization code has expired.", "provider_error": "access_denied", @@ -12499,6 +13008,7 @@ def test_pipes_connected_account_connection_failed_data_preserves_nullable_field "provider_slug": "github", "user_id": None, "organization_id": None, + "account_identifier": None, "error_code": "authorization_code_exchange_error", "error_reason": None, "provider_error": None, @@ -12509,6 +13019,7 @@ def test_pipes_connected_account_connection_failed_data_preserves_nullable_field serialized = instance.to_dict() assert serialized["user_id"] is None assert serialized["organization_id"] is None + assert serialized["account_identifier"] is None assert serialized["error_reason"] is None assert serialized["provider_error"] is None assert serialized["provider_error_description"] is None @@ -12529,11 +13040,16 @@ def test_pipes_connected_account_disconnected_minimal_payload(self): "data": { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, "state": "connected", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", @@ -12558,11 +13074,16 @@ def test_pipes_connected_account_disconnected_omits_absent_optional_non_nullable "data": { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, "state": "connected", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", @@ -12589,11 +13110,16 @@ def test_pipes_connected_account_reauthorization_needed_minimal_payload(self): "data": { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, "state": "connected", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", @@ -12618,11 +13144,16 @@ def test_pipes_connected_account_reauthorization_needed_omits_absent_optional_no "data": { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "data_integration_id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", "provider_slug": "github", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, "state": "connected", "created_at": "2026-01-15T12:00:00.000Z", "updated_at": "2026-01-15T12:00:00.000Z", diff --git a/tests/test_organizations_data_providers.py b/tests/test_organizations_data_providers.py new file mode 100644 index 000000000..062d01eff --- /dev/null +++ b/tests/test_organizations_data_providers.py @@ -0,0 +1,501 @@ +# This file is auto-generated by oagen. Do not edit. + + +import pytest + +from tests.generated_helpers import load_fixture +from workos import AsyncWorkOSClient, WorkOSClient +from workos._errors import ( + AuthenticationError, + BadRequestError, + NotFoundError, + RateLimitExceededError, + ServerError, + UnprocessableEntityError, +) +from workos.common.models import ConnectedAccount, DataIntegrationsListResponse + + +class TestOrganizationsDataProviders: + def test_get_organization_connected_account(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("connected_account.json"), + ) + result = workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert isinstance(result, ConnectedAccount) + assert result.object == "connected_account" + assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + def test_get_organization_connected_account_encodes_query_params( + self, workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", + "test_slug", + supports_multiple_connections=True, + connected_account_id="value connected_account_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) + + def test_create_organization_connected_account(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("connected_account.json"), + ) + result = ( + workos.organizations_data_providers.create_organization_connected_account( + "test_organization_id", "test_slug" + ) + ) + assert isinstance(result, ConnectedAccount) + assert result.object == "connected_account" + assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + def test_update_organization_connected_account(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("connected_account.json"), + ) + result = ( + workos.organizations_data_providers.update_organization_connected_account( + "test_organization_id", "test_slug" + ) + ) + assert isinstance(result, ConnectedAccount) + assert result.object == "connected_account" + assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "PUT" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + def test_update_organization_connected_account_encodes_query_params( + self, workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + workos.organizations_data_providers.update_organization_connected_account( + "test_organization_id", + "test_slug", + supports_multiple_connections=True, + connected_account_id="value connected_account_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) + + def test_delete_organization_connected_account(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = ( + workos.organizations_data_providers.delete_organization_connected_account( + "test_organization_id", "test_slug" + ) + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + def test_delete_organization_connected_account_encodes_query_params( + self, workos, httpx_mock + ): + httpx_mock.add_response(status_code=204) + workos.organizations_data_providers.delete_organization_connected_account( + "test_organization_id", + "test_slug", + supports_multiple_connections=True, + connected_account_id="value connected_account_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) + + def test_list_organization_data_providers(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("data_integrations_list_response.json"), + ) + result = workos.organizations_data_providers.list_organization_data_providers( + "test_organization_id" + ) + assert isinstance(result, DataIntegrationsListResponse) + assert result.object == "list" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/organizations/test_organization_id/data_providers" + ) + + def test_list_organization_data_providers_encodes_query_params( + self, workos, httpx_mock + ): + httpx_mock.add_response( + json=load_fixture("data_integrations_list_response.json") + ) + workos.organizations_data_providers.list_organization_data_providers( + "test_organization_id", supports_multiple_connections=True + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + + def test_get_organization_connected_account_with_request_options( + self, workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", + "test_slug", + request_options={"extra_headers": {"X-Custom": "value"}}, + ) + request = httpx_mock.get_request() + assert request.headers["X-Custom"] == "value" + + def test_get_organization_connected_account_unauthorized(self, workos, httpx_mock): + httpx_mock.add_response( + status_code=401, + json={"message": "Unauthorized"}, + ) + with pytest.raises(AuthenticationError): + workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + + def test_get_organization_connected_account_not_found(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=404, json={"message": "Not found"}) + with pytest.raises(NotFoundError): + workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + finally: + workos.close() + + def test_get_organization_connected_account_rate_limited(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response( + status_code=429, + headers={"Retry-After": "0"}, + json={"message": "Slow down"}, + ) + with pytest.raises(RateLimitExceededError): + workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + finally: + workos.close() + + def test_get_organization_connected_account_server_error(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=500, json={"message": "Server error"}) + with pytest.raises(ServerError): + workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + finally: + workos.close() + + def test_get_organization_connected_account_bad_request(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=400, json={"message": "Bad request"}) + with pytest.raises(BadRequestError): + workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + finally: + workos.close() + + def test_get_organization_connected_account_unprocessable(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=422, json={"message": "Unprocessable"}) + with pytest.raises(UnprocessableEntityError): + workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + finally: + workos.close() + + +class TestAsyncOrganizationsDataProviders: + @pytest.mark.asyncio + async def test_get_organization_connected_account(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + result = await async_workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert isinstance(result, ConnectedAccount) + assert result.object == "connected_account" + assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + @pytest.mark.asyncio + async def test_get_organization_connected_account_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + await async_workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", + "test_slug", + supports_multiple_connections=True, + connected_account_id="value connected_account_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) + + @pytest.mark.asyncio + async def test_create_organization_connected_account( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + result = await async_workos.organizations_data_providers.create_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert isinstance(result, ConnectedAccount) + assert result.object == "connected_account" + assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + @pytest.mark.asyncio + async def test_update_organization_connected_account( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + result = await async_workos.organizations_data_providers.update_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert isinstance(result, ConnectedAccount) + assert result.object == "connected_account" + assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "PUT" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + @pytest.mark.asyncio + async def test_update_organization_connected_account_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + await async_workos.organizations_data_providers.update_organization_connected_account( + "test_organization_id", + "test_slug", + supports_multiple_connections=True, + connected_account_id="value connected_account_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) + + @pytest.mark.asyncio + async def test_delete_organization_connected_account( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(status_code=204) + result = await async_workos.organizations_data_providers.delete_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + @pytest.mark.asyncio + async def test_delete_organization_connected_account_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(status_code=204) + await async_workos.organizations_data_providers.delete_organization_connected_account( + "test_organization_id", + "test_slug", + supports_multiple_connections=True, + connected_account_id="value connected_account_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) + + @pytest.mark.asyncio + async def test_list_organization_data_providers(self, async_workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("data_integrations_list_response.json") + ) + result = await async_workos.organizations_data_providers.list_organization_data_providers( + "test_organization_id" + ) + assert isinstance(result, DataIntegrationsListResponse) + assert result.object == "list" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/organizations/test_organization_id/data_providers" + ) + + @pytest.mark.asyncio + async def test_list_organization_data_providers_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response( + json=load_fixture("data_integrations_list_response.json") + ) + await ( + async_workos.organizations_data_providers.list_organization_data_providers( + "test_organization_id", supports_multiple_connections=True + ) + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + + @pytest.mark.asyncio + async def test_get_organization_connected_account_with_request_options( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + await async_workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", + "test_slug", + request_options={"extra_headers": {"X-Custom": "value"}}, + ) + request = httpx_mock.get_request() + assert request.headers["X-Custom"] == "value" + + @pytest.mark.asyncio + async def test_get_organization_connected_account_unauthorized( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(status_code=401, json={"message": "Unauthorized"}) + with pytest.raises(AuthenticationError): + await async_workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + + @pytest.mark.asyncio + async def test_get_organization_connected_account_not_found(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=404, json={"message": "Not found"}) + with pytest.raises(NotFoundError): + await workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_get_organization_connected_account_rate_limited(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response( + status_code=429, + headers={"Retry-After": "0"}, + json={"message": "Slow down"}, + ) + with pytest.raises(RateLimitExceededError): + await workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_get_organization_connected_account_server_error(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=500, json={"message": "Server error"}) + with pytest.raises(ServerError): + await workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_get_organization_connected_account_bad_request(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=400, json={"message": "Bad request"}) + with pytest.raises(BadRequestError): + await workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_get_organization_connected_account_unprocessable(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=422, json={"message": "Unprocessable"}) + with pytest.raises(UnprocessableEntityError): + await workos.organizations_data_providers.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + finally: + await workos.close() diff --git a/tests/test_organizations_data_providers_models_round_trip.py b/tests/test_organizations_data_providers_models_round_trip.py new file mode 100644 index 000000000..3d6733a93 --- /dev/null +++ b/tests/test_organizations_data_providers_models_round_trip.py @@ -0,0 +1,441 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Model round-trip tests: from_dict(to_dict()) preserves data.""" + +from tests.generated_helpers import load_fixture +from workos.organizations_data_providers.models import ( + DataIntegrationsListResponse, + DataIntegrationsListResponseData, + DataIntegrationsListResponseDataConnectedAccount, +) + + +class TestModelRoundTrip: + def test_data_integrations_list_response_round_trip(self): + data = load_fixture("data_integrations_list_response.json") + instance = DataIntegrationsListResponse.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationsListResponse.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integrations_list_response_minimal_payload(self): + data = { + "object": "list", + "data": [ + { + "object": "data_provider", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "name": "GitHub", + "description": "Connect your GitHub account to access repositories.", + "slug": "github", + "integration_type": "github", + "credentials_type": "oauth2", + "scopes": ["repo", "user:email"], + "auth_methods": ["oauth"], + "connection_owner": "user", + "ownership": "userland_user", + "created_at": "2024-01-15T10:30:00.000Z", + "updated_at": "2024-01-15T10:30:00.000Z", + "connected_account": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + }, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": { + "instance_url": "https://example.my.salesforce.com" + }, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], + } + ], + } + instance = DataIntegrationsListResponse.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["data"] == data["data"] + + def test_data_integrations_list_response_data_round_trip(self): + data = load_fixture("data_integrations_list_response_data.json") + instance = DataIntegrationsListResponseData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationsListResponseData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integrations_list_response_data_minimal_payload(self): + data = { + "object": "data_provider", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "name": "GitHub", + "description": None, + "slug": "github", + "integration_type": "github", + "credentials_type": "oauth2", + "scopes": None, + "connection_owner": "user", + "ownership": "userland_user", + "created_at": "2024-01-15T10:30:00.000Z", + "updated_at": "2024-01-15T10:30:00.000Z", + "connected_account": None, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], + } + instance = DataIntegrationsListResponseData.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["name"] == data["name"] + assert serialized["description"] == data["description"] + assert serialized["slug"] == data["slug"] + assert serialized["integration_type"] == data["integration_type"] + assert serialized["credentials_type"] == data["credentials_type"] + assert serialized["scopes"] == data["scopes"] + assert serialized["connection_owner"] == data["connection_owner"] + assert serialized["ownership"] == data["ownership"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + assert serialized["connected_account"] == data["connected_account"] + assert serialized["connected_accounts"] == data["connected_accounts"] + + def test_data_integrations_list_response_data_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "object": "data_provider", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "name": "GitHub", + "description": "Connect your GitHub account to access repositories.", + "slug": "github", + "integration_type": "github", + "credentials_type": "oauth2", + "scopes": ["repo", "user:email"], + "connection_owner": "user", + "ownership": "userland_user", + "created_at": "2024-01-15T10:30:00.000Z", + "updated_at": "2024-01-15T10:30:00.000Z", + "connected_account": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + }, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], + } + instance = DataIntegrationsListResponseData.from_dict(data) + serialized = instance.to_dict() + assert "auth_methods" not in serialized + + def test_data_integrations_list_response_data_preserves_nullable_fields(self): + data = { + "object": "data_provider", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "name": "GitHub", + "description": None, + "slug": "github", + "integration_type": "github", + "credentials_type": "oauth2", + "scopes": None, + "auth_methods": ["oauth"], + "connection_owner": "user", + "ownership": "userland_user", + "created_at": "2024-01-15T10:30:00.000Z", + "updated_at": "2024-01-15T10:30:00.000Z", + "connected_account": None, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], + } + instance = DataIntegrationsListResponseData.from_dict(data) + serialized = instance.to_dict() + assert serialized["description"] is None + assert serialized["scopes"] is None + assert serialized["connected_account"] is None + + def test_data_integrations_list_response_data_round_trips_unknown_enum_values(self): + data = { + "object": "data_provider", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "name": "GitHub", + "description": "Connect your GitHub account to access repositories.", + "slug": "github", + "integration_type": "github", + "credentials_type": "oauth2", + "scopes": ["repo", "user:email"], + "auth_methods": ["oauth"], + "connection_owner": "unexpected_data_integrations_list_response_data_connection_owner", + "ownership": "userland_user", + "created_at": "2024-01-15T10:30:00.000Z", + "updated_at": "2024-01-15T10:30:00.000Z", + "connected_account": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + }, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], + } + instance = DataIntegrationsListResponseData.from_dict(data) + assert instance.to_dict() == data + + def test_data_integrations_list_response_data_connected_account_round_trip(self): + data = load_fixture( + "data_integrations_list_response_data_connected_account.json" + ) + instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationsListResponseDataConnectedAccount.from_dict( + serialized + ) + assert restored.to_dict() == serialized + + def test_data_integrations_list_response_data_connected_account_minimal_payload( + self, + ): + data = { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": None, + "account_display_name": None, + "user_id": None, + "organization_id": None, + "scopes": ["repo", "user:email"], + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": None, + } + instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["connection_role"] == data["connection_role"] + assert serialized["account_identifier"] == data["account_identifier"] + assert serialized["account_display_name"] == data["account_display_name"] + assert serialized["user_id"] == data["user_id"] + assert serialized["organization_id"] == data["organization_id"] + assert serialized["scopes"] == data["scopes"] + assert serialized["state"] == data["state"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + assert serialized["userlandUserId"] == data["userlandUserId"] + + def test_data_integrations_list_response_data_connected_account_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) + serialized = instance.to_dict() + assert "auth_method" not in serialized + assert "config" not in serialized + + def test_data_integrations_list_response_data_connected_account_preserves_nullable_fields( + self, + ): + data = { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": None, + "account_display_name": None, + "user_id": None, + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": None, + "client_secret_last_4": None, + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": None, + } + instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) + serialized = instance.to_dict() + assert serialized["account_identifier"] is None + assert serialized["account_display_name"] is None + assert serialized["user_id"] is None + assert serialized["organization_id"] is None + assert serialized["api_key_last_4"] is None + assert serialized["client_id"] is None + assert serialized["client_secret_last_4"] is None + assert serialized["userlandUserId"] is None + + def test_data_integrations_list_response_data_connected_account_round_trips_unknown_enum_values( + self, + ): + data = { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "unexpected_data_integrations_list_response_data_connected_account_connection_role", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) + assert instance.to_dict() == data diff --git a/tests/test_pipes.py b/tests/test_pipes.py index 9955f1154..35f3cf8e9 100644 --- a/tests/test_pipes.py +++ b/tests/test_pipes.py @@ -17,6 +17,7 @@ from workos._pagination import AsyncPage, SyncPage from workos.common.models import ( ConnectedAccount, + DataIntegrationsListResponse, PaginationOrder, ) from workos.pipes.models import ( @@ -24,7 +25,6 @@ DataIntegrationAccessTokenResponse, DataIntegrationAuthorizeUrlResponse, DataIntegrationCredentialsResponse, - DataIntegrationsListResponse, PipesOwnership, ) diff --git a/tests/test_pipes_models_round_trip.py b/tests/test_pipes_models_round_trip.py index cab9c812d..2ee1f2f07 100644 --- a/tests/test_pipes_models_round_trip.py +++ b/tests/test_pipes_models_round_trip.py @@ -17,9 +17,6 @@ DataIntegrationCredentialsResponseCredential, DataIntegrationCustomProvider, DataIntegrationInstallation, - DataIntegrationsListResponse, - DataIntegrationsListResponseData, - DataIntegrationsListResponseDataConnectedAccount, UpdateCustomProviderDefinition, ) @@ -439,6 +436,9 @@ def test_connected_account_minimal_payload(self): data = { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": None, + "account_display_name": None, "user_id": None, "organization_id": None, "scopes": ["repo", "user:email"], @@ -450,6 +450,9 @@ def test_connected_account_minimal_payload(self): serialized = instance.to_dict() assert serialized["object"] == data["object"] assert serialized["id"] == data["id"] + assert serialized["connection_role"] == data["connection_role"] + assert serialized["account_identifier"] == data["account_identifier"] + assert serialized["account_display_name"] == data["account_display_name"] assert serialized["user_id"] == data["user_id"] assert serialized["organization_id"] == data["organization_id"] assert serialized["scopes"] == data["scopes"] @@ -461,6 +464,9 @@ def test_connected_account_omits_absent_optional_non_nullable_fields(self): data = { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": None, "scopes": ["repo", "user:email"], @@ -480,6 +486,9 @@ def test_connected_account_preserves_nullable_fields(self): data = { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": None, + "account_display_name": None, "user_id": None, "organization_id": None, "scopes": ["repo", "user:email"], @@ -494,6 +503,8 @@ def test_connected_account_preserves_nullable_fields(self): } instance = ConnectedAccount.from_dict(data) serialized = instance.to_dict() + assert serialized["account_identifier"] is None + assert serialized["account_display_name"] is None assert serialized["user_id"] is None assert serialized["organization_id"] is None assert serialized["api_key_last_4"] is None @@ -504,10 +515,13 @@ def test_connected_account_round_trips_unknown_enum_values(self): data = { "object": "connected_account", "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "unexpected_connected_account_connection_role", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", "organization_id": None, "scopes": ["repo", "user:email"], - "auth_method": "unexpected_connected_account_auth_method", + "auth_method": "oauth", "api_key_last_4": None, "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", "client_secret_last_4": "cdef", @@ -519,278 +533,6 @@ def test_connected_account_round_trips_unknown_enum_values(self): instance = ConnectedAccount.from_dict(data) assert instance.to_dict() == data - def test_data_integrations_list_response_round_trip(self): - data = load_fixture("data_integrations_list_response.json") - instance = DataIntegrationsListResponse.from_dict(data) - serialized = instance.to_dict() - assert serialized == data - restored = DataIntegrationsListResponse.from_dict(serialized) - assert restored.to_dict() == serialized - - def test_data_integrations_list_response_minimal_payload(self): - data = { - "object": "list", - "data": [ - { - "object": "data_provider", - "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", - "name": "GitHub", - "description": "Connect your GitHub account to access repositories.", - "slug": "github", - "integration_type": "github", - "credentials_type": "oauth2", - "scopes": ["repo", "user:email"], - "auth_methods": ["oauth"], - "ownership": "userland_user", - "created_at": "2024-01-15T10:30:00.000Z", - "updated_at": "2024-01-15T10:30:00.000Z", - "connected_account": { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - }, - "connected_accounts": [ - { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": { - "instance_url": "https://example.my.salesforce.com" - }, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - ], - } - ], - } - instance = DataIntegrationsListResponse.from_dict(data) - serialized = instance.to_dict() - assert serialized["object"] == data["object"] - assert serialized["data"] == data["data"] - - def test_data_integrations_list_response_data_round_trip(self): - data = load_fixture("data_integrations_list_response_data.json") - instance = DataIntegrationsListResponseData.from_dict(data) - serialized = instance.to_dict() - assert serialized == data - restored = DataIntegrationsListResponseData.from_dict(serialized) - assert restored.to_dict() == serialized - - def test_data_integrations_list_response_data_minimal_payload(self): - data = { - "object": "data_provider", - "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", - "name": "GitHub", - "description": None, - "slug": "github", - "integration_type": "github", - "credentials_type": "oauth2", - "scopes": None, - "ownership": "userland_user", - "created_at": "2024-01-15T10:30:00.000Z", - "updated_at": "2024-01-15T10:30:00.000Z", - "connected_account": None, - "connected_accounts": [ - { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - ], - } - instance = DataIntegrationsListResponseData.from_dict(data) - serialized = instance.to_dict() - assert serialized["object"] == data["object"] - assert serialized["id"] == data["id"] - assert serialized["name"] == data["name"] - assert serialized["description"] == data["description"] - assert serialized["slug"] == data["slug"] - assert serialized["integration_type"] == data["integration_type"] - assert serialized["credentials_type"] == data["credentials_type"] - assert serialized["scopes"] == data["scopes"] - assert serialized["ownership"] == data["ownership"] - assert serialized["created_at"] == data["created_at"] - assert serialized["updated_at"] == data["updated_at"] - assert serialized["connected_account"] == data["connected_account"] - assert serialized["connected_accounts"] == data["connected_accounts"] - - def test_data_integrations_list_response_data_omits_absent_optional_non_nullable_fields( - self, - ): - data = { - "object": "data_provider", - "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", - "name": "GitHub", - "description": "Connect your GitHub account to access repositories.", - "slug": "github", - "integration_type": "github", - "credentials_type": "oauth2", - "scopes": ["repo", "user:email"], - "ownership": "userland_user", - "created_at": "2024-01-15T10:30:00.000Z", - "updated_at": "2024-01-15T10:30:00.000Z", - "connected_account": { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - }, - "connected_accounts": [ - { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - ], - } - instance = DataIntegrationsListResponseData.from_dict(data) - serialized = instance.to_dict() - assert "auth_methods" not in serialized - - def test_data_integrations_list_response_data_preserves_nullable_fields(self): - data = { - "object": "data_provider", - "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", - "name": "GitHub", - "description": None, - "slug": "github", - "integration_type": "github", - "credentials_type": "oauth2", - "scopes": None, - "auth_methods": ["oauth"], - "ownership": "userland_user", - "created_at": "2024-01-15T10:30:00.000Z", - "updated_at": "2024-01-15T10:30:00.000Z", - "connected_account": None, - "connected_accounts": [ - { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - ], - } - instance = DataIntegrationsListResponseData.from_dict(data) - serialized = instance.to_dict() - assert serialized["description"] is None - assert serialized["scopes"] is None - assert serialized["connected_account"] is None - - def test_data_integrations_list_response_data_round_trips_unknown_enum_values(self): - data = { - "object": "data_provider", - "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", - "name": "GitHub", - "description": "Connect your GitHub account to access repositories.", - "slug": "github", - "integration_type": "github", - "credentials_type": "oauth2", - "scopes": ["repo", "user:email"], - "auth_methods": ["oauth"], - "ownership": "unexpected_data_integrations_list_response_data_ownership", - "created_at": "2024-01-15T10:30:00.000Z", - "updated_at": "2024-01-15T10:30:00.000Z", - "connected_account": { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - }, - "connected_accounts": [ - { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - ], - } - instance = DataIntegrationsListResponseData.from_dict(data) - assert instance.to_dict() == data - def test_data_integration_credentials_response_credential_round_trip(self): data = load_fixture("data_integration_credentials_response_credential.json") instance = DataIntegrationCredentialsResponseCredential.from_dict(data) @@ -913,6 +655,9 @@ def test_data_integration_installation_round_trip(self): def test_data_integration_installation_minimal_payload(self): data = { "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": None, + "account_display_name": None, "user_id": None, "organization_id": None, "api_key_last_4": None, @@ -920,6 +665,9 @@ def test_data_integration_installation_minimal_payload(self): instance = DataIntegrationInstallation.from_dict(data) serialized = instance.to_dict() assert serialized["id"] == data["id"] + assert serialized["connection_role"] == data["connection_role"] + assert serialized["account_identifier"] == data["account_identifier"] + assert serialized["account_display_name"] == data["account_display_name"] assert serialized["user_id"] == data["user_id"] assert serialized["organization_id"] == data["organization_id"] assert serialized["api_key_last_4"] == data["api_key_last_4"] @@ -927,16 +675,34 @@ def test_data_integration_installation_minimal_payload(self): def test_data_integration_installation_preserves_nullable_fields(self): data = { "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": None, + "account_display_name": None, "user_id": None, "organization_id": None, "api_key_last_4": None, } instance = DataIntegrationInstallation.from_dict(data) serialized = instance.to_dict() + assert serialized["account_identifier"] is None + assert serialized["account_display_name"] is None assert serialized["user_id"] is None assert serialized["organization_id"] is None assert serialized["api_key_last_4"] is None + def test_data_integration_installation_round_trips_unknown_enum_values(self): + data = { + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "unexpected_data_integration_installation_connection_role", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "api_key_last_4": "cdef", + } + instance = DataIntegrationInstallation.from_dict(data) + assert instance.to_dict() == data + def test_data_integration_custom_provider_round_trip(self): data = load_fixture("data_integration_custom_provider.json") instance = DataIntegrationCustomProvider.from_dict(data) @@ -1012,113 +778,3 @@ def test_data_integration_custom_provider_round_trips_unknown_enum_values(self): } instance = DataIntegrationCustomProvider.from_dict(data) assert instance.to_dict() == data - - def test_data_integrations_list_response_data_connected_account_round_trip(self): - data = load_fixture( - "data_integrations_list_response_data_connected_account.json" - ) - instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) - serialized = instance.to_dict() - assert serialized == data - restored = DataIntegrationsListResponseDataConnectedAccount.from_dict( - serialized - ) - assert restored.to_dict() == serialized - - def test_data_integrations_list_response_data_connected_account_minimal_payload( - self, - ): - data = { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": None, - "organization_id": None, - "scopes": ["repo", "user:email"], - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": None, - } - instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) - serialized = instance.to_dict() - assert serialized["object"] == data["object"] - assert serialized["id"] == data["id"] - assert serialized["user_id"] == data["user_id"] - assert serialized["organization_id"] == data["organization_id"] - assert serialized["scopes"] == data["scopes"] - assert serialized["state"] == data["state"] - assert serialized["created_at"] == data["created_at"] - assert serialized["updated_at"] == data["updated_at"] - assert serialized["userlandUserId"] == data["userlandUserId"] - - def test_data_integrations_list_response_data_connected_account_omits_absent_optional_non_nullable_fields( - self, - ): - data = { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) - serialized = instance.to_dict() - assert "auth_method" not in serialized - assert "config" not in serialized - - def test_data_integrations_list_response_data_connected_account_preserves_nullable_fields( - self, - ): - data = { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": None, - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": None, - "client_secret_last_4": None, - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": None, - } - instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) - serialized = instance.to_dict() - assert serialized["user_id"] is None - assert serialized["organization_id"] is None - assert serialized["api_key_last_4"] is None - assert serialized["client_id"] is None - assert serialized["client_secret_last_4"] is None - assert serialized["userlandUserId"] is None - - def test_data_integrations_list_response_data_connected_account_round_trips_unknown_enum_values( - self, - ): - data = { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "unexpected_data_integrations_list_response_data_connected_account_auth_method", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) - assert instance.to_dict() == data From ce01fae75c87d7ff86b25580d2bf02f99613a332 Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Thu, 17 Sep 2026 09:43:42 -0400 Subject: [PATCH 6/9] feat: switch default HTTP client to httpx2 and accept a custom http_client (#731) --- README.md | 75 +++++ pyproject.toml | 6 +- src/workos/__init__.py | 14 + src/workos/_base_client.py | 155 ++++++--- src/workos/_http.py | 331 ++++++++++++++++++++ src/workos/public_client.py | 11 +- tests/conftest.py | 98 ++++++ tests/smoke_test.py | 4 +- tests/test_generated_client.py | 2 +- tests/test_http_backends.py | 554 +++++++++++++++++++++++++++++++++ uv.lock | 89 ++++-- 11 files changed, 1267 insertions(+), 72 deletions(-) create mode 100644 src/workos/_http.py create mode 100644 tests/test_http_backends.py diff --git a/README.md b/README.md index 633f9bf17..0d7da15ae 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,81 @@ except RateLimitExceededError as e: The client automatically retries requests up to 3 times (configurable via the `max_retries` request option) on 429 and 5xx responses, timeouts, and connection errors, using exponential backoff with jitter and honoring `Retry-After`. The SDK attaches an auto-generated `Idempotency-Key` (UUID v4) to every `POST` request and reuses the same key across its internal retries. +## HTTP Backends + +The SDK sends requests through [`httpx2`](https://pypi.org/project/httpx2/) by default. Pass your own configured client as `http_client` to control proxies, TLS, connection limits, or transports: + +```python +import httpx2 +from workos import AsyncWorkOSClient, WorkOSClient + +client = WorkOSClient( + api_key="sk_...", + http_client=httpx2.Client(proxy="http://proxy.internal:3128", verify="/etc/ssl/corp.pem"), +) + +async_client = AsyncWorkOSClient( + api_key="sk_...", + http_client=httpx2.AsyncClient(limits=httpx2.Limits(max_connections=20)), +) +``` + +`httpx` 0.28 clients are accepted as well; the two libraries share an API. The SDK closes only clients it created. A client you pass in stays open after `client.close()`, so close it yourself when you are done with it. + +### Custom backends + +Any object implementing `workos.HTTPBackend` (or `workos.AsyncHTTPBackend` for the async client) can be passed as `http_client`. The SDK hands the backend a fully built URL, the final headers, an optional `bytes` body, and a timeout in seconds, and expects a `workos.HTTPResponse` back. Raise `workos.TransportTimeout`, `workos.TransportConnectError`, or `workos.TransportError` for network failures so the SDK's retry logic can handle them. The `headers` mapping on the response must be case-insensitive. + +The following `aiohttp` adapter is an example, not a supported part of the SDK: + +```python +import asyncio + +import aiohttp +import yarl + +from workos import ( + AsyncWorkOSClient, + HTTPResponse, + TransportConnectError, + TransportError, + TransportTimeout, +) + + +class AiohttpBackend: + def __init__(self, session: aiohttp.ClientSession) -> None: + self._session = session + + async def request(self, method, url, *, headers, content, timeout) -> HTTPResponse: + try: + async with self._session.request( + method, + yarl.URL(url, encoded=True), # keep the SDK's percent-encoding intact + headers=headers, + data=content, + timeout=aiohttp.ClientTimeout(total=timeout), + allow_redirects=True, + ) as resp: + body = await resp.read() + return HTTPResponse(resp.status, resp.headers, body, method, str(resp.url)) + except (asyncio.TimeoutError, aiohttp.ServerTimeoutError) as exc: # before connection errors + raise TransportTimeout(str(exc)) from exc + except aiohttp.ClientConnectionError as exc: + raise TransportConnectError(str(exc)) from exc + except aiohttp.ClientError as exc: + raise TransportError(str(exc)) from exc + + async def close(self) -> None: + await self._session.close() + + +async def main() -> None: + async with aiohttp.ClientSession() as session: + client = AsyncWorkOSClient(api_key="sk_...", http_client=AiohttpBackend(session)) + page = await client.organizations.list_organizations() +``` + ## Per-Request Options Every API method accepts `request_options` for per-call overrides (local helpers such as webhook/Actions signature verification and PKCE utilities do not make HTTP calls and don't take `request_options`): diff --git a/pyproject.toml b/pyproject.toml index 5f4f3439b..6eb4608b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ requires-python = ">=3.10" dependencies = [ "cryptography~=50.0", - "httpx~=0.28", + "httpx2~=2.13", "pyjwt~=2.12", "typing_extensions~=4.0; python_version < '3.11'", ] @@ -31,10 +31,10 @@ test = [ "pytest~=9.0", "pytest-asyncio~=1.3", "pytest-cov~=7.1", - "pytest-httpx~=0.36", + "httpx~=0.28", ] lint = ["ruff~=0.15"] -type_check = ["pyright~=1.1"] +type_check = ["pyright~=1.1", "httpx~=0.28"] nox = ["nox~=2026.2", "nox-uv~=0.7"] docs = ["pdoc>=14", "pydoc-markdown>=4", "black~=26.3"] diff --git a/src/workos/__init__.py b/src/workos/__init__.py index faa676adf..1a1182f45 100644 --- a/src/workos/__init__.py +++ b/src/workos/__init__.py @@ -14,6 +14,14 @@ ServerError, UnprocessableEntityError, ) +from ._http import ( + AsyncHTTPBackend, + HTTPBackend, + HTTPResponse, + TransportConnectError, + TransportError, + TransportTimeout, +) from ._pagination import AsyncPage, ListMetadata, SyncPage from .public_client import create_public_client from ._types import NOT_GIVEN, NotGiven, RequestOptions @@ -37,4 +45,10 @@ "NOT_GIVEN", "NotGiven", "create_public_client", + "HTTPBackend", + "AsyncHTTPBackend", + "HTTPResponse", + "TransportError", + "TransportTimeout", + "TransportConnectError", ] diff --git a/src/workos/_base_client.py b/src/workos/_base_client.py index bb1f17a3f..24a907d49 100644 --- a/src/workos/_base_client.py +++ b/src/workos/_base_client.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +import json import os import platform import time @@ -10,9 +11,7 @@ from datetime import datetime, timezone from email.utils import parsedate_to_datetime from typing import Any, Dict, Optional, Sequence, Type, cast, overload -from urllib.parse import quote - -import httpx +from urllib.parse import quote, urlencode from ._errors import ( APIError, @@ -25,6 +24,16 @@ STATUS_CODE_TO_ERROR, _AUTH_CODE_TO_ERROR, ) +from ._http import ( + AsyncHTTPClient, + HTTPResponse, + SyncHTTPClient, + TransportConnectError, + TransportError, + TransportTimeout, + resolve_async_backend, + resolve_sync_backend, +) from ._pagination import AsyncPage, ListMetadata, SyncPage from ._types import D, Deserializable, RequestOptions @@ -96,8 +105,6 @@ def build_url( self, path: Sequence[str], params: Optional[Dict[str, Any]] = None ) -> str: """Build a full URL with query parameters for redirect/authorization endpoints.""" - from urllib.parse import urlencode - base = self._base_url.rstrip("/") url = f"{base}/{self._encode_path(path)}" if params: @@ -153,8 +160,8 @@ def _encode_path(path: Sequence[str]) -> str: Percent-encoding alone does not cover dot segments: ``.`` is an unreserved character that :func:`urllib.parse.quote` leaves as-is, and - httpx applies RFC 3986 dot-segment removal when it builds the request - URL, so a bare ``.`` or ``..`` segment would collapse the path onto the + HTTP clients such as httpx2 apply RFC 3986 dot-segment removal when they + build the request URL, so a bare ``.`` or ``..`` segment would collapse the path onto the parent resource (for example, turning a DELETE of one connected account into a DELETE of the whole user). Empty, ``.`` and ``..`` segments are never valid WorkOS identifiers, so they are rejected with @@ -194,6 +201,57 @@ def _resolve_max_retries(self, request_options: Optional[RequestOptions]) -> int return retries return self._max_retries + @staticmethod + def _query_value(value: Any) -> str: + """Stringify one query value the way httpx does.""" + if value is True: + return "true" + if value is False: + return "false" + if value is None: + return "" + return str(value) + + @staticmethod + def _encode_query(params: Optional[Dict[str, Any]]) -> str: + """Encode query parameters into a query string. + + Mirrors httpx 0.28 so behaviour is identical for every HTTP backend: + booleans become ``true`` / ``false``, ``None`` becomes an empty value, + lists and tuples repeat the key, everything else is ``str()``-ed. + Generated resources rely on this for raw ``bool``, ``int`` and ``list`` + values. + """ + if not params: + return "" + pairs: list[tuple[str, str]] = [] + for key, value in params.items(): + if isinstance(value, (list, tuple)): + for item in cast(Sequence[Any], value): + pairs.append((key, _BaseWorkOSClient._query_value(item))) + else: + pairs.append((key, _BaseWorkOSClient._query_value(value))) + return urlencode(pairs) + + @staticmethod + def _encode_body(body: Optional[Dict[str, Any]]) -> Optional[bytes]: + """Serialize a JSON body in httpx's compact form, or ``None`` for no body.""" + if body is None: + return None + return json.dumps( + body, ensure_ascii=False, separators=(",", ":"), allow_nan=False + ).encode("utf-8") + + def _build_request_url( + self, + path: Sequence[str], + params: Optional[Dict[str, Any]], + request_options: Optional[RequestOptions], + ) -> str: + url = f"{self._resolve_base_url(request_options)}/{self._encode_path(path)}" + query = self._encode_query(params) + return f"{url}?{query}" if query else url + def _require_api_key(self) -> str: if not self._api_key: raise ConfigurationError( @@ -238,7 +296,7 @@ def _build_headers( return headers def _deserialize_response( - self, response: httpx.Response, model: Optional[Type[Deserializable]] + self, response: HTTPResponse, model: Optional[Type[Deserializable]] ) -> Any: if response.status_code == 204 or not response.content: return None @@ -251,13 +309,12 @@ def _deserialize_response( return data @staticmethod - def _raise_error(response: httpx.Response) -> None: + def _raise_error(response: HTTPResponse) -> None: """Raise an appropriate error based on the response status code.""" request_id = response.headers.get("x-request-id", "") raw_body = response.text - request = response.request - request_url = str(request.url) if request is not None else None - request_method = request.method if request is not None else None + request_url = response.request_url + request_method = response.request_method response_json: Optional[Dict[str, Any]] = None try: response_json = cast(Dict[str, Any], response.json()) @@ -384,6 +441,7 @@ def __init__( jwt_leeway: float = 0.0, max_retries: int = MAX_RETRIES, is_public: bool = False, + http_client: Optional[SyncHTTPClient] = None, ) -> None: """Initialize the WorkOS client. @@ -398,9 +456,15 @@ def __init__( / mobile / CLI). The API key is forced to None and the ``WORKOS_API_KEY`` environment variable is ignored. Use ``create_public_client`` instead of setting this directly. + http_client: HTTP client to send requests with. Accepts an + ``httpx2.Client``, an ``httpx.Client``, or any object implementing + :class:`workos.HTTPBackend`. Defaults to a new ``httpx2.Client`` + that this instance owns and closes. A client you pass in is + never closed by the SDK. Raises: ValueError: If neither api_key nor client_id is provided, directly or via environment variables. + TypeError: If ``http_client`` is not a supported client type. """ super().__init__( api_key=api_key, @@ -411,13 +475,17 @@ def __init__( max_retries=max_retries, is_public=is_public, ) - self._client = httpx.Client( - timeout=self._request_timeout, follow_redirects=True + self._backend, self._owns_http_client = resolve_sync_backend( + http_client, self._request_timeout ) def close(self) -> None: - """Close the underlying HTTP client and release resources.""" - self._client.close() + """Release the HTTP client if the SDK created it. + + A client passed in via ``http_client`` stays open; close it yourself. + """ + if self._owns_http_client: + self._backend.close() def __enter__(self) -> "WorkOSClient": return self @@ -463,19 +531,19 @@ def request( request_options: Optional[RequestOptions] = None, ) -> Any: """Make an HTTP request with retry logic.""" - url = f"{self._resolve_base_url(request_options)}/{self._encode_path(path)}" + url = self._build_request_url(path, params, request_options) headers = self._build_headers(method, idempotency_key, request_options) + content = self._encode_body(body) timeout = self._resolve_timeout(request_options) max_retries = self._resolve_max_retries(request_options) last_error: Optional[Exception] = None for attempt in range(max_retries + 1): try: - response = self._client.request( - method=method.upper(), - url=url, - params=params, - json=body if body is not None else None, + response = self._backend.request( + method.upper(), + url, headers=headers, + content=content, timeout=timeout, ) if response.status_code in RETRY_STATUS_CODES and attempt < max_retries: @@ -487,19 +555,19 @@ def request( if response.status_code >= 400: self._raise_error(response) return self._deserialize_response(response, model) - except httpx.TimeoutException as e: + except TransportTimeout as e: last_error = e if attempt < max_retries: time.sleep(self._calculate_retry_delay(attempt)) continue raise WorkOSTimeoutError(f"Request timed out: {e}") from e - except httpx.ConnectError as e: + except TransportConnectError as e: last_error = e if attempt < max_retries: time.sleep(self._calculate_retry_delay(attempt)) continue raise WorkOSConnectionError(f"Connection failed: {e}") from e - except httpx.HTTPError as e: + except TransportError as e: last_error = e if attempt < max_retries: time.sleep(self._calculate_retry_delay(attempt)) @@ -615,6 +683,7 @@ def __init__( jwt_leeway: float = 0.0, max_retries: int = MAX_RETRIES, is_public: bool = False, + http_client: Optional[AsyncHTTPClient] = None, ) -> None: """Initialize the async WorkOS client. @@ -625,9 +694,15 @@ def __init__( request_timeout: HTTP request timeout in seconds. Falls back to WORKOS_REQUEST_TIMEOUT or 60. jwt_leeway: JWT clock skew leeway in seconds. max_retries: Maximum number of retries for failed requests. Defaults to 3. + http_client: HTTP client to send requests with. Accepts an + ``httpx2.AsyncClient``, an ``httpx.AsyncClient``, or any object + implementing :class:`workos.AsyncHTTPBackend`. Defaults to a new + ``httpx2.AsyncClient`` that this instance owns and closes. A + client you pass in is never closed by the SDK. Raises: ValueError: If neither api_key nor client_id is provided, directly or via environment variables. + TypeError: If ``http_client`` is not a supported client type. """ super().__init__( api_key=api_key, @@ -638,13 +713,17 @@ def __init__( max_retries=max_retries, is_public=is_public, ) - self._client = httpx.AsyncClient( - timeout=self._request_timeout, follow_redirects=True + self._backend, self._owns_http_client = resolve_async_backend( + http_client, self._request_timeout ) async def close(self) -> None: - """Close the underlying HTTP client and release resources.""" - await self._client.aclose() + """Release the HTTP client if the SDK created it. + + A client passed in via ``http_client`` stays open; close it yourself. + """ + if self._owns_http_client: + await self._backend.close() async def __aenter__(self) -> "AsyncWorkOSClient": return self @@ -690,19 +769,19 @@ async def request( request_options: Optional[RequestOptions] = None, ) -> Any: """Make an async HTTP request with retry logic.""" - url = f"{self._resolve_base_url(request_options)}/{self._encode_path(path)}" + url = self._build_request_url(path, params, request_options) headers = self._build_headers(method, idempotency_key, request_options) + content = self._encode_body(body) timeout = self._resolve_timeout(request_options) max_retries = self._resolve_max_retries(request_options) last_error: Optional[Exception] = None for attempt in range(max_retries + 1): try: - response = await self._client.request( - method=method.upper(), - url=url, - params=params, - json=body if body is not None else None, + response = await self._backend.request( + method.upper(), + url, headers=headers, + content=content, timeout=timeout, ) if response.status_code in RETRY_STATUS_CODES and attempt < max_retries: @@ -714,19 +793,19 @@ async def request( if response.status_code >= 400: self._raise_error(response) return self._deserialize_response(response, model) - except httpx.TimeoutException as e: + except TransportTimeout as e: last_error = e if attempt < max_retries: await asyncio.sleep(self._calculate_retry_delay(attempt)) continue raise WorkOSTimeoutError(f"Request timed out: {e}") from e - except httpx.ConnectError as e: + except TransportConnectError as e: last_error = e if attempt < max_retries: await asyncio.sleep(self._calculate_retry_delay(attempt)) continue raise WorkOSConnectionError(f"Connection failed: {e}") from e - except httpx.HTTPError as e: + except TransportError as e: last_error = e if attempt < max_retries: await asyncio.sleep(self._calculate_retry_delay(attempt)) diff --git a/src/workos/_http.py b/src/workos/_http.py new file mode 100644 index 000000000..13d70dbdc --- /dev/null +++ b/src/workos/_http.py @@ -0,0 +1,331 @@ +# @oagen-ignore-file +"""HTTP transport layer for the WorkOS client. + +``_base_client`` owns URL building, query and body encoding, retries and error +mapping. Everything that touches a concrete HTTP library lives here, behind the +:class:`HTTPBackend` / :class:`AsyncHTTPBackend` protocols, so a caller can +supply any HTTP client they like via ``http_client=``. +""" + +from __future__ import annotations + +import importlib +import inspect +import json +from dataclasses import dataclass +from typing import ( + Any, + Dict, + Mapping, + Optional, + Protocol, + Tuple, + Union, + cast, +) +from urllib.parse import urlsplit + +import httpx2 + +try: + import httpx +except ModuleNotFoundError as exc: + if exc.name != "httpx": + raise + # Keep runtime annotations resolvable without the optional legacy package. + import httpx2 as httpx + + +class TransportError(Exception): + """Network-level failure reported by an HTTP backend. + + Backends raise this family instead of their library's own exceptions so the + retry loop in ``_base_client`` never depends on a particular HTTP client. + """ + + +class TransportTimeout(TransportError): + """The request timed out.""" + + +class TransportConnectError(TransportError): + """A connection could not be established.""" + + +@dataclass(frozen=True) +class HTTPResponse: + """Backend-neutral view of an HTTP response. + + ``headers`` must be a case-insensitive mapping (``httpx2.Headers`` and + ``multidict.CIMultiDictProxy`` both qualify): the SDK reads ``Retry-After`` + and ``x-request-id`` without normalising case. + """ + + status_code: int + headers: Mapping[str, str] + content: bytes + request_method: str + request_url: str + + @property + def text(self) -> str: + """Body decoded as UTF-8, replacing undecodable bytes.""" + return self.content.decode("utf-8", errors="replace") + + def json(self) -> Any: + """Body parsed as JSON.""" + return json.loads(self.content) + + +class HTTPBackend(Protocol): + """Synchronous transport used by :class:`workos.WorkOSClient`. + + The SDK passes a fully built URL (path and query already encoded), the final + headers, an optional JSON body as bytes, and a timeout in seconds. Raise + :class:`TransportTimeout`, :class:`TransportConnectError` or + :class:`TransportError` for network failures so the SDK can retry them. + """ + + def request( + self, + method: str, + url: str, + *, + headers: Dict[str, str], + content: Optional[bytes], + timeout: float, + ) -> HTTPResponse: ... + + def close(self) -> None: ... + + +class AsyncHTTPBackend(Protocol): + """Asynchronous transport used by :class:`workos.AsyncWorkOSClient`. + + Same contract as :class:`HTTPBackend` with coroutine methods. + """ + + async def request( + self, + method: str, + url: str, + *, + headers: Dict[str, str], + content: Optional[bytes], + timeout: float, + ) -> HTTPResponse: ... + + async def close(self) -> None: ... + + +SyncHTTPClient = Union[httpx2.Client, httpx.Client, HTTPBackend] +AsyncHTTPClient = Union[httpx2.AsyncClient, httpx.AsyncClient, AsyncHTTPBackend] + + +# --- httpx family ------------------------------------------------------------- + +_HTTPX_MODULES = frozenset({"httpx2", "httpx"}) + + +def _httpx_root(obj: object) -> Optional[str]: + """Return ``"httpx2"`` or ``"httpx"`` if ``obj`` is (a subclass of) a client from that module.""" + for cls in type(obj).__mro__: + root = cls.__module__.partition(".")[0] + if root in _HTTPX_MODULES: + return root + return None + + +class _HttpxFamily: + """Classes from whichever httpx-compatible module a client was built with. + + ``httpx2`` is a fork of ``httpx`` 0.28 with an identical API, so one adapter + serves both. Only the exception and client classes differ by module. + """ + + def __init__(self, module_name: str) -> None: + module = importlib.import_module(module_name) + self.name = module_name + self.client: type[Any] = module.Client + self.async_client: type[Any] = module.AsyncClient + self.timeout_error: type[Exception] = module.TimeoutException + self.connect_error: type[Exception] = module.ConnectError + self.http_error: type[Exception] = module.HTTPError + + +def _wrap_response(response: Union[httpx2.Response, httpx.Response]) -> HTTPResponse: + request = response.request + return HTTPResponse( + status_code=response.status_code, + headers=response.headers, + content=response.content, + request_method=request.method, + request_url=str(request.url), + ) + + +class HttpxBackend: + """Adapter for ``httpx2.Client`` and the API-identical ``httpx.Client``.""" + + def __init__(self, client: Union[httpx2.Client, httpx.Client]) -> None: + self._client = client + self._family = _HttpxFamily(_httpx_root(client) or "httpx2") + + def request( + self, + method: str, + url: str, + *, + headers: Dict[str, str], + content: Optional[bytes], + timeout: float, + ) -> HTTPResponse: + try: + response = self._client.request( + method, + url, + # Explicit params merge defaults instead of replacing the URL query. + params=urlsplit(url).query or None, + headers=headers, + content=content, + timeout=timeout, + ) + except self._family.timeout_error as exc: + raise TransportTimeout(str(exc)) from exc + except self._family.connect_error as exc: + raise TransportConnectError(str(exc)) from exc + except self._family.http_error as exc: + raise TransportError(str(exc)) from exc + return _wrap_response(response) + + def close(self) -> None: + self._client.close() + + +class AsyncHttpxBackend: + """Adapter for ``httpx2.AsyncClient`` and the API-identical ``httpx.AsyncClient``.""" + + def __init__(self, client: Union[httpx2.AsyncClient, httpx.AsyncClient]) -> None: + self._client = client + self._family = _HttpxFamily(_httpx_root(client) or "httpx2") + + async def request( + self, + method: str, + url: str, + *, + headers: Dict[str, str], + content: Optional[bytes], + timeout: float, + ) -> HTTPResponse: + try: + response = await self._client.request( + method, + url, + # Explicit params merge defaults instead of replacing the URL query. + params=urlsplit(url).query or None, + headers=headers, + content=content, + timeout=timeout, + ) + except self._family.timeout_error as exc: + raise TransportTimeout(str(exc)) from exc + except self._family.connect_error as exc: + raise TransportConnectError(str(exc)) from exc + except self._family.http_error as exc: + raise TransportError(str(exc)) from exc + return _wrap_response(response) + + async def close(self) -> None: + await self._client.aclose() + + +# --- resolution ----------------------------------------------------------------- + + +def _describe(obj: object) -> str: + return f"{type(obj).__module__}.{type(obj).__qualname__}" + + +def _is_backend_like(obj: object) -> bool: + return callable(getattr(obj, "request", None)) and callable( + getattr(obj, "close", None) + ) + + +def _has_async_request(obj: object) -> bool: + return inspect.iscoroutinefunction(getattr(obj, "request", None)) + + +_UNSUPPORTED = ( + "Unsupported http_client {desc}: pass an httpx2 client, an httpx client, or an " + "object implementing workos.{protocol} (request() and close())." +) + + +def resolve_sync_backend( + http_client: Optional[SyncHTTPClient], timeout: float +) -> Tuple[HTTPBackend, bool]: + """Return ``(backend, owned)`` for :class:`workos.WorkOSClient`. + + ``owned`` is True only for the default client the SDK creates itself. The + SDK never closes a client the caller passed in. + """ + if http_client is None: + client = httpx2.Client(timeout=timeout, follow_redirects=True) + return HttpxBackend(client), True + root = _httpx_root(http_client) + if root is not None: + family = _HttpxFamily(root) + if isinstance(http_client, family.client): + return HttpxBackend(cast(httpx2.Client, http_client)), False + if isinstance(http_client, family.async_client): + raise TypeError( + f"WorkOSClient needs a synchronous HTTP client but got " + f"{root}.AsyncClient; pass {root}.Client, or use AsyncWorkOSClient." + ) + if _is_backend_like(http_client): + if _has_async_request(http_client): + raise TypeError( + "WorkOSClient needs an HTTPBackend with a synchronous request(); " + f"{_describe(http_client)}.request is a coroutine function. " + "Use AsyncWorkOSClient." + ) + return cast(HTTPBackend, http_client), False + raise TypeError( + _UNSUPPORTED.format(desc=_describe(http_client), protocol="HTTPBackend") + ) + + +def resolve_async_backend( + http_client: Optional[AsyncHTTPClient], timeout: float +) -> Tuple[AsyncHTTPBackend, bool]: + """Return ``(backend, owned)`` for :class:`workos.AsyncWorkOSClient`. + + ``owned`` is True only for the default client the SDK creates itself. The + SDK never closes a client the caller passed in. + """ + if http_client is None: + client = httpx2.AsyncClient(timeout=timeout, follow_redirects=True) + return AsyncHttpxBackend(client), True + root = _httpx_root(http_client) + if root is not None: + family = _HttpxFamily(root) + if isinstance(http_client, family.async_client): + return AsyncHttpxBackend(cast(httpx2.AsyncClient, http_client)), False + if isinstance(http_client, family.client): + raise TypeError( + f"AsyncWorkOSClient needs an asynchronous HTTP client but got " + f"{root}.Client; pass {root}.AsyncClient, or use WorkOSClient." + ) + if _is_backend_like(http_client): + if not _has_async_request(http_client): + raise TypeError( + "AsyncWorkOSClient needs an AsyncHTTPBackend whose request() is a " + f"coroutine function; {_describe(http_client)}.request is not. " + "Use WorkOSClient." + ) + return cast(AsyncHTTPBackend, http_client), False + raise TypeError( + _UNSUPPORTED.format(desc=_describe(http_client), protocol="AsyncHTTPBackend") + ) diff --git a/src/workos/public_client.py b/src/workos/public_client.py index b4bca338b..e4ee11e91 100644 --- a/src/workos/public_client.py +++ b/src/workos/public_client.py @@ -4,10 +4,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import Optional -if TYPE_CHECKING: - from ._client import WorkOSClient +from ._client import WorkOSClient +from ._http import SyncHTTPClient def create_public_client( @@ -15,6 +15,7 @@ def create_public_client( client_id: str, base_url: Optional[str] = None, request_timeout: Optional[int] = None, + http_client: Optional["SyncHTTPClient"] = None, ) -> "WorkOSClient": """Create a WorkOS client configured for public/PKCE-only usage. @@ -26,16 +27,16 @@ def create_public_client( client_id: The WorkOS client ID. base_url: Override the base URL. Defaults to ``https://api.workos.com``. request_timeout: HTTP request timeout in seconds. + http_client: HTTP client to send requests with; see :class:`workos.WorkOSClient`. Returns: A WorkOSClient instance with only ``client_id`` configured. """ - from ._client import WorkOSClient - return WorkOSClient( api_key=None, client_id=client_id, base_url=base_url, request_timeout=request_timeout, is_public=True, + http_client=http_client, ) diff --git a/tests/conftest.py b/tests/conftest.py index 065ed01c4..9c22d5ffc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,10 @@ # @oagen-ignore-file +from __future__ import annotations + +from typing import Any, Dict, Iterator, List, Optional, Union + +import httpx2 import pytest import pytest_asyncio @@ -26,3 +31,96 @@ async def async_workos(): yield client finally: await client.close() + + +class HTTPXMock: + """Stand-in for the ``pytest-httpx`` fixture of the same name. + + ``pytest-httpx`` pins ``httpx==0.28.*`` and cannot intercept ``httpx2``. The + oagen-generated test files depend on this fixture's name and on the subset of + its API implemented here, so the shim is hand-maintained instead of changing + the emitter. Requests are intercepted the same way ``pytest-httpx`` does, by + patching the transport classes, so clients constructed directly in a test are + covered too. Responses are consumed first-in first-out. Like ``pytest-httpx``, + the fixture fails the test at teardown if any queued response was never + requested; call ``reset()`` to discard the queue deliberately. + """ + + def __init__(self) -> None: + self._queue: List[Union[httpx2.Response, Exception]] = [] + self._requests: List[httpx2.Request] = [] + + def add_response( + self, + *, + json: Any = None, + status_code: int = 200, + headers: Optional[Dict[str, str]] = None, + content: Optional[bytes] = None, + ) -> None: + self._queue.append( + httpx2.Response(status_code, json=json, headers=headers, content=content) + ) + + def add_exception(self, exception: Exception) -> None: + self._queue.append(exception) + + def get_requests(self) -> List[httpx2.Request]: + return list(self._requests) + + def get_request(self) -> Optional[httpx2.Request]: + if len(self._requests) > 1: + raise AssertionError( + f"{len(self._requests)} requests were sent; use get_requests()" + ) + return self._requests[0] if self._requests else None + + def reset(self) -> None: + self._queue.clear() + self._requests.clear() + + def unused_responses(self) -> int: + return len(self._queue) + + def _next(self, request: httpx2.Request) -> httpx2.Response: + self._requests.append(request) + if not self._queue: + raise httpx2.TimeoutException( + "No response registered for this request", request=request + ) + item = self._queue.pop(0) + if isinstance(item, Exception): + raise item + return item + + def handle_request(self, request: httpx2.Request) -> httpx2.Response: + request.read() + return self._next(request) + + async def handle_async_request(self, request: httpx2.Request) -> httpx2.Response: + await request.aread() + return self._next(request) + + +@pytest.fixture +def httpx_mock(monkeypatch: pytest.MonkeyPatch) -> Iterator[HTTPXMock]: + mock = HTTPXMock() + + def _sync( + _transport: httpx2.HTTPTransport, request: httpx2.Request + ) -> httpx2.Response: + return mock.handle_request(request) + + async def _async( + _transport: httpx2.AsyncHTTPTransport, request: httpx2.Request + ) -> httpx2.Response: + return await mock.handle_async_request(request) + + monkeypatch.setattr(httpx2.HTTPTransport, "handle_request", _sync) + monkeypatch.setattr(httpx2.AsyncHTTPTransport, "handle_async_request", _async) + yield mock + unused = mock.unused_responses() + if unused: + pytest.fail( + f"{unused} httpx_mock response(s) were registered but never requested" + ) diff --git a/tests/smoke_test.py b/tests/smoke_test.py index 52833dbb1..e630a96fb 100644 --- a/tests/smoke_test.py +++ b/tests/smoke_test.py @@ -201,10 +201,10 @@ def test_pagination_importable() -> None: def test_dependencies_available() -> None: """Verify core runtime dependencies are installed and importable.""" import cryptography - import httpx + import httpx2 import jwt - print("✓ Core dependencies available (httpx, cryptography, pyjwt)") + print("✓ Core dependencies available (httpx2, cryptography, pyjwt)") def main() -> int: diff --git a/tests/test_generated_client.py b/tests/test_generated_client.py index 2847631fd..3dcdd66f0 100644 --- a/tests/test_generated_client.py +++ b/tests/test_generated_client.py @@ -2,7 +2,7 @@ """Client tests: retries, errors, context manager, idempotency.""" -import httpx +import httpx2 as httpx import pytest from workos import WorkOSClient, AsyncWorkOSClient diff --git a/tests/test_http_backends.py b/tests/test_http_backends.py new file mode 100644 index 000000000..5c27a9e6a --- /dev/null +++ b/tests/test_http_backends.py @@ -0,0 +1,554 @@ +# @oagen-ignore-file + +"""HTTP backend protocol, the httpx/httpx2 adapter, resolution, and encoding parity.""" + +from __future__ import annotations + +import json +import subprocess +import sys +from typing import Any, Dict, List, Optional, Union + +import httpx +import httpx2 +import pytest + +from workos import ( + AsyncWorkOSClient, + HTTPResponse, + NotFoundError, + TransportConnectError, + TransportError, + TransportTimeout, + WorkOSClient, + WorkOSError, +) +from workos import _base_client as base_client_module +from workos._base_client import _BaseWorkOSClient +from workos._errors import WorkOSConnectionError, WorkOSTimeoutError +from workos._http import AsyncHttpxBackend, HttpxBackend +from tests.generated_helpers import load_fixture + +API_KEY = "sk_test_123" +BASE = "https://api.workos.com" + + +def make_response( + status_code: int = 200, + body: Any = None, + headers: Optional[Dict[str, str]] = None, + method: str = "GET", + url: str = f"{BASE}/t", +) -> HTTPResponse: + content = b"" if body is None else json.dumps(body).encode() + return HTTPResponse( + status_code=status_code, + headers=httpx2.Headers(headers or {}), + content=content, + request_method=method, + request_url=url, + ) + + +class FakeBackend: + """Minimal HTTPBackend that replays queued responses or exceptions.""" + + def __init__(self, *items: Union[HTTPResponse, Exception]) -> None: + self.items: List[Union[HTTPResponse, Exception]] = list(items) + self.calls: List[Dict[str, Any]] = [] + self.closed = False + + def _record( + self, + method: str, + url: str, + headers: Dict[str, str], + content: Optional[bytes], + timeout: float, + ) -> HTTPResponse: + self.calls.append( + { + "method": method, + "url": url, + "headers": headers, + "content": content, + "timeout": timeout, + } + ) + item = self.items.pop(0) + if isinstance(item, Exception): + raise item + return item + + def request( + self, + method: str, + url: str, + *, + headers: Dict[str, str], + content: Optional[bytes], + timeout: float, + ) -> HTTPResponse: + return self._record(method, url, headers, content, timeout) + + def close(self) -> None: + self.closed = True + + +class AsyncFakeBackend(FakeBackend): + """Async twin of FakeBackend.""" + + async def request( # type: ignore[override] + self, + method: str, + url: str, + *, + headers: Dict[str, str], + content: Optional[bytes], + timeout: float, + ) -> HTTPResponse: + return self._record(method, url, headers, content, timeout) + + async def close(self) -> None: # type: ignore[override] + self.closed = True + + +@pytest.fixture(autouse=True) +def no_sleep(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(base_client_module.time, "sleep", lambda _: None) + + +class TestProtocolBackend: + def test_receives_full_url_headers_body_and_timeout(self) -> None: + backend = FakeBackend(make_response(200, {"ok": True})) + client = WorkOSClient(api_key=API_KEY, http_client=backend) + + result = client.request( + "POST", + ("orgs", "org 1"), + params={"limit": 10, "enabled": True, "tags": ["a", "b"]}, + body={"name": "Acme"}, + request_options={"timeout": 7}, + ) + + assert result == {"ok": True} + call = backend.calls[0] + assert call["method"] == "POST" + assert call["url"] == f"{BASE}/orgs/org%201?limit=10&enabled=true&tags=a&tags=b" + assert call["content"] == b'{"name":"Acme"}' + assert call["headers"]["Content-Type"] == "application/json" + assert call["headers"]["Authorization"] == f"Bearer {API_KEY}" + assert call["timeout"] == 7.0 + + def test_no_params_and_no_body(self) -> None: + backend = FakeBackend(make_response(204)) + client = WorkOSClient(api_key=API_KEY, http_client=backend) + + assert client.request("GET", ("t",)) is None + assert backend.calls[0]["url"] == f"{BASE}/t" + assert backend.calls[0]["content"] is None + + def test_retries_retryable_status_then_succeeds(self) -> None: + backend = FakeBackend( + make_response(429, {"message": "slow"}, {"Retry-After": "0"}), + make_response(200, {"ok": True}), + ) + client = WorkOSClient(api_key=API_KEY, http_client=backend) + + assert client.request("GET", ("t",)) == {"ok": True} + assert len(backend.calls) == 2 + + def test_client_error_is_raised_not_retried(self) -> None: + backend = FakeBackend( + make_response(404, {"message": "missing"}, {"X-Request-Id": "req_1"}) + ) + client = WorkOSClient(api_key=API_KEY, http_client=backend, max_retries=3) + + with pytest.raises(NotFoundError) as exc_info: + client.request("GET", ("t",)) + + assert exc_info.value.request_id == "req_1" + assert exc_info.value.request_url == f"{BASE}/t" + assert exc_info.value.request_method == "GET" + assert len(backend.calls) == 1 + + @pytest.mark.parametrize( + ("transport_error", "sdk_error", "message"), + [ + (TransportTimeout("slow"), WorkOSTimeoutError, "Request timed out: slow"), + ( + TransportConnectError("refused"), + WorkOSConnectionError, + "Connection failed: refused", + ), + (TransportError("broken"), WorkOSError, "Network error: broken"), + ], + ) + def test_transport_errors_are_mapped( + self, transport_error: Exception, sdk_error: type, message: str + ) -> None: + backend = FakeBackend(transport_error) + client = WorkOSClient(api_key=API_KEY, http_client=backend, max_retries=0) + + with pytest.raises(sdk_error) as exc_info: + client.request("GET", ("t",)) + + assert str(exc_info.value) == message + assert exc_info.value.__cause__ is transport_error + + def test_transport_errors_are_retried(self) -> None: + backend = FakeBackend(TransportTimeout("slow"), make_response(200, {"ok": 1})) + client = WorkOSClient(api_key=API_KEY, http_client=backend, max_retries=1) + + assert client.request("GET", ("t",)) == {"ok": 1} + assert len(backend.calls) == 2 + + def test_injected_backend_is_not_closed(self) -> None: + backend = FakeBackend() + client = WorkOSClient(api_key=API_KEY, http_client=backend) + + client.close() + + assert backend.closed is False + + def test_default_client_is_closed(self) -> None: + client = WorkOSClient(api_key=API_KEY) + backend = client._backend + assert isinstance(backend, HttpxBackend) + + client.close() + + assert backend._client.is_closed + + +@pytest.mark.asyncio +class TestAsyncProtocolBackend: + async def test_retries_and_deserializes(self) -> None: + backend = AsyncFakeBackend( + make_response(503, {"message": "down"}, {"Retry-After": "0"}), + make_response(200, {"ok": True}), + ) + client = AsyncWorkOSClient(api_key=API_KEY, http_client=backend) + + assert await client.request("GET", ("t",)) == {"ok": True} + assert len(backend.calls) == 2 + assert backend.calls[0]["url"] == f"{BASE}/t" + + async def test_transport_timeout_is_mapped(self) -> None: + backend = AsyncFakeBackend(TransportTimeout("slow")) + client = AsyncWorkOSClient(api_key=API_KEY, http_client=backend, max_retries=0) + + with pytest.raises(WorkOSTimeoutError, match="Request timed out: slow"): + await client.request("GET", ("t",)) + + async def test_client_error_is_raised_not_retried(self) -> None: + backend = AsyncFakeBackend(make_response(404, {"message": "missing"})) + client = AsyncWorkOSClient(api_key=API_KEY, http_client=backend) + + with pytest.raises(NotFoundError): + await client.request("GET", ("t",)) + + assert len(backend.calls) == 1 + + async def test_injected_backend_is_not_closed(self) -> None: + backend = AsyncFakeBackend() + client = AsyncWorkOSClient(api_key=API_KEY, http_client=backend) + + await client.close() + + assert backend.closed is False + + async def test_default_client_is_closed(self) -> None: + client = AsyncWorkOSClient(api_key=API_KEY) + backend = client._backend + assert isinstance(backend, AsyncHttpxBackend) + + await client.close() + + assert backend._client.is_closed + + +MODULES = [pytest.param(httpx2, id="httpx2"), pytest.param(httpx, id="httpx")] + + +@pytest.mark.parametrize("mod", MODULES) +class TestHttpxAdapter: + def test_wraps_response(self, mod: Any) -> None: + seen: Dict[str, Any] = {} + + def handler(request: Any) -> Any: + seen["timeout"] = request.extensions.get("timeout") + seen["content"] = request.content + return mod.Response(200, json={"a": 1}, headers={"X-Request-Id": "req_1"}) + + backend = HttpxBackend(mod.Client(transport=mod.MockTransport(handler))) + response = backend.request( + "POST", "https://x/p?q=1", headers={"H": "v"}, content=b"{}", timeout=5.0 + ) + + assert isinstance(response, HTTPResponse) + assert response.status_code == 200 + assert response.json() == {"a": 1} + assert response.headers.get("x-request-id") == "req_1" + assert response.request_method == "POST" + assert response.request_url == "https://x/p?q=1" + assert seen["content"] == b"{}" + assert seen["timeout"] == { + "connect": 5.0, + "read": 5.0, + "write": 5.0, + "pool": 5.0, + } + + @pytest.mark.parametrize( + ("raised", "expected"), + [ + ("TimeoutException", TransportTimeout), + ("ConnectError", TransportConnectError), + ("RemoteProtocolError", TransportError), + ], + ) + def test_maps_exceptions(self, mod: Any, raised: str, expected: type) -> None: + exc_cls = getattr(mod, raised) + + def handler(request: Any) -> Any: + raise exc_cls("boom") + + backend = HttpxBackend(mod.Client(transport=mod.MockTransport(handler))) + + with pytest.raises(expected) as exc_info: + backend.request("GET", "https://x/p", headers={}, content=None, timeout=1.0) + + assert isinstance(exc_info.value.__cause__, exc_cls) + + def test_end_to_end_through_client(self, mod: Any) -> None: + def handler(request: Any) -> Any: + return mod.Response(404, json={"message": "nope"}) + + http_client = mod.Client(transport=mod.MockTransport(handler)) + client = WorkOSClient(api_key=API_KEY, http_client=http_client) + + with pytest.raises(NotFoundError): + client.request("GET", ("t",)) + client.close() + + assert not http_client.is_closed + http_client.close() + + @pytest.mark.asyncio + async def test_async_end_to_end_through_client(self, mod: Any) -> None: + def handler(request: Any) -> Any: + return mod.Response(200, json={"ok": True}) + + http_client = mod.AsyncClient(transport=mod.MockTransport(handler)) + client = AsyncWorkOSClient(api_key=API_KEY, http_client=http_client) + + assert await client.request("GET", ("t",)) == {"ok": True} + await client.close() + + assert not http_client.is_closed + await http_client.aclose() + + @pytest.mark.asyncio + @pytest.mark.parametrize("asynchronous", [False, True], ids=["sync", "async"]) + async def test_client_query_defaults_preserve_filters_and_pagination( + self, mod: Any, asynchronous: bool + ) -> None: + requests: List[Any] = [] + first_page = load_fixture("list_user.json") + first_page["list_metadata"]["after"] = "cursor/after" + + def handler(request: Any) -> Any: + requests.append(request) + body = ( + first_page if len(requests) == 1 else {"data": [], "list_metadata": {}} + ) + return mod.Response(200, json=body) + + defaults = {"limit": 100, "order": "asc", "organization_id": "org_default"} + http_cls = mod.AsyncClient if asynchronous else mod.Client + http_client = http_cls(params=defaults, transport=mod.MockTransport(handler)) + try: + if asynchronous: + client = AsyncWorkOSClient(api_key=API_KEY, http_client=http_client) + page = await client.user_management.list_users( + email="alice@example.com", limit=7 + ) + users = [user async for user in page] + else: + sync_client = WorkOSClient(api_key=API_KEY, http_client=http_client) + sync_page = sync_client.user_management.list_users( + email="alice@example.com", limit=7 + ) + users = list(sync_page) + finally: + if asynchronous: + await http_client.aclose() + else: + http_client.close() + + expected = { + "limit": "7", + "order": "desc", + "organization_id": "org_default", + "email": "alice@example.com", + } + assert len(users) == 1 + assert len(requests) == 2 + assert dict(requests[0].url.params) == expected + assert dict(requests[1].url.params) == {**expected, "after": "cursor/after"} + assert http_client.params == mod.QueryParams(defaults) + + @pytest.mark.asyncio + async def test_async_adapter_maps_timeout(self, mod: Any) -> None: + def handler(request: Any) -> Any: + raise mod.TimeoutException("slow") + + backend = AsyncHttpxBackend( + mod.AsyncClient(transport=mod.MockTransport(handler)) + ) + + with pytest.raises(TransportTimeout): + await backend.request( + "GET", "https://x/p", headers={}, content=None, timeout=1.0 + ) + + +class TestResolution: + @pytest.mark.parametrize( + "httpx_installed", [False, True], ids=["without-httpx", "with-httpx"] + ) + def test_public_client_annotations_resolve_at_runtime( + self, httpx_installed: bool + ) -> None: + script = """ +import sys +from typing import get_args, get_type_hints + +if sys.argv[1] == "False": + sys.modules["httpx"] = None + +import httpx2 +from workos import ( + AsyncHTTPBackend, AsyncWorkOSClient, HTTPBackend, WorkOSClient, create_public_client, +) + +for factory, client_type, protocol in ( + (WorkOSClient.__init__, httpx2.Client, HTTPBackend), + (AsyncWorkOSClient.__init__, httpx2.AsyncClient, AsyncHTTPBackend), + (create_public_client, httpx2.Client, HTTPBackend), +): + client_types = get_args(get_type_hints(factory)["http_client"]) + assert client_type in client_types + assert protocol in client_types + assert type(None) in client_types + if sys.argv[1] == "True": + import httpx + assert getattr(httpx, client_type.__name__) in client_types + +assert get_type_hints(create_public_client)["return"] is WorkOSClient +""" + result = subprocess.run( + [sys.executable, "-c", script, str(httpx_installed)], + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, result.stdout + result.stderr + + def test_default_backend_settings(self) -> None: + client = WorkOSClient(api_key=API_KEY, request_timeout=9) + backend = client._backend + assert isinstance(backend, HttpxBackend) + assert isinstance(backend._client, httpx2.Client) + assert backend._client.follow_redirects is True + assert backend._client.timeout == httpx2.Timeout(9) + client.close() + + def test_httpx_client_subclass_is_detected(self) -> None: + class MyClient(httpx2.Client): + pass + + http_client = MyClient() + client = WorkOSClient(api_key=API_KEY, http_client=http_client) + assert isinstance(client._backend, HttpxBackend) + http_client.close() + + def test_sync_client_rejects_async_httpx_client(self) -> None: + with pytest.raises(TypeError, match="use AsyncWorkOSClient"): + WorkOSClient(api_key=API_KEY, http_client=httpx2.AsyncClient()) # type: ignore[arg-type] + + def test_async_client_rejects_sync_httpx_client(self) -> None: + with pytest.raises(TypeError, match="use WorkOSClient"): + AsyncWorkOSClient(api_key=API_KEY, http_client=httpx2.Client()) # type: ignore[arg-type] + + def test_sync_client_rejects_async_backend(self) -> None: + with pytest.raises(TypeError, match="coroutine"): + WorkOSClient(api_key=API_KEY, http_client=AsyncFakeBackend()) # type: ignore[arg-type] + + def test_async_client_rejects_sync_backend(self) -> None: + with pytest.raises(TypeError, match="coroutine"): + AsyncWorkOSClient(api_key=API_KEY, http_client=FakeBackend()) # type: ignore[arg-type] + + def test_rejects_unrelated_object(self) -> None: + with pytest.raises(TypeError, match="HTTPBackend"): + WorkOSClient(api_key=API_KEY, http_client=object()) # type: ignore[arg-type] + + +class TestEncodingParity: + @pytest.mark.parametrize( + "params", + [ + {"limit": 10}, + {"enabled": True, "archived": False}, + {"after": None}, + {"domains": ["a.com", "b.com"]}, + {"order": ("asc", "desc")}, + {"search": "héllo wörld & co=1"}, + {"empty": ""}, + {"ratio": 1.5}, + {}, + None, + ], + ) + def test_query_matches_httpx(self, params: Optional[Dict[str, Any]]) -> None: + encoded = _BaseWorkOSClient._encode_query(params) + ours = httpx2.URL(f"{BASE}/p?{encoded}" if encoded else f"{BASE}/p") + theirs = httpx2.Request("GET", f"{BASE}/p", params=params).url + + assert ours.params == theirs.params + assert str(ours) == str(theirs) + with httpx2.Client( + transport=httpx2.MockTransport(lambda _: httpx2.Response(200)) + ) as client: + response = HttpxBackend(client).request( + "GET", str(ours), headers={}, content=None, timeout=1.0 + ) + assert response.request_url == str(theirs) + + def test_query_string_shape(self) -> None: + params = {"a": True, "b": None, "c": ["x", "y"], "d": 5} + assert _BaseWorkOSClient._encode_query(params) == "a=true&b=&c=x&c=y&d=5" + + def test_body_matches_httpx(self) -> None: + body = {"a": 1, "b": [1, 2], "s": "é", "nested": {"k": None}} + assert ( + _BaseWorkOSClient._encode_body(body) + == httpx2.Request("POST", BASE, json=body).content + ) + assert _BaseWorkOSClient._encode_body(None) is None + assert _BaseWorkOSClient._encode_body({}) == b"{}" + + def test_recorded_request_exposes_decoded_params( + self, workos: WorkOSClient, httpx_mock: Any + ) -> None: + httpx_mock.add_response(json={}) + + workos.request( + "GET", ("t",), params={"enabled": True, "limit": 5, "ids": ["a", "b"]} + ) + + request = httpx_mock.get_request() + assert request.url.params["enabled"] == "true" + assert request.url.params["limit"] == "5" + assert request.url.params.get_list("ids") == ["a", "b"] diff --git a/uv.lock b/uv.lock index 9f66a5756..3ca7bf04c 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' or sys_platform != 'emscripten'", +] [[package]] name = "anyio" @@ -629,6 +633,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httpcore2" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/8c/e925b1c92018abb3a1863ce1549d76d2381e334d21d65d4ac8f65dabd78a/httpcore2-2.13.0.tar.gz", hash = "sha256:2adc8be4fb285fbcd6d894298db3b52c177e74b6674eda3a76bd36be3292a3db", size = 67740, upload-time = "2026-09-14T14:18:04.717Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/0d/117a771a2bb91df334b66bf4da14cd02f21aefbcfe53180f336ce55e8f90/httpcore2-2.13.0-py3-none-any.whl", hash = "sha256:35ae5be347aa40467b4a5dc032ac67ebb6d27189fc97e8cebcf99616f6a1bb9e", size = 83162, upload-time = "2026-09-14T14:18:02.529Z" }, +] + [[package]] name = "httpx" version = "0.28.1" @@ -644,6 +661,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[[package]] +name = "httpx2" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform != 'emscripten'" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" }, + { name = "idna" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/a0/e9deef4654132857b5a5dbe4eddd0ac59c2814500e11f2f5044cd81103ee/httpx2-2.13.0.tar.gz", hash = "sha256:81bd07dc67a3701729ef1f777a3c00c915d4539604fdb5afd327f8682f6b7b44", size = 100290, upload-time = "2026-09-14T14:18:05.486Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/d1/a0c72b0e006df654709fbc366cc5bcb53e5aee13e1e3395152c6dd293376/httpx2-2.13.0-py3-none-any.whl", hash = "sha256:fc12720cedf72faa26cca6b4ca394e05c894e7d7933fc45cafe767960804e49a", size = 95565, upload-time = "2026-09-14T14:18:03.553Z" }, +] + +[[package]] +name = "httpx2-jsfetch" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, +] + [[package]] name = "humanize" version = "4.15.0" @@ -655,11 +698,11 @@ wheels = [ [[package]] name = "idna" -version = "3.11" +version = "3.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] [[package]] @@ -1022,19 +1065,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] -[[package]] -name = "pytest-httpx" -version = "0.36.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "httpx" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/5574834da9499066fa1a5ea9c336f94dba2eae02298d36dab192fcf95c86/pytest_httpx-0.36.0.tar.gz", hash = "sha256:9edb66a5fd4388ce3c343189bc67e7e1cb50b07c2e3fc83b97d511975e8a831b", size = 56793, upload-time = "2025-12-02T16:34:57.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/d2/1eb1ea9c84f0d2033eb0b49675afdc71aa4ea801b74615f00f3c33b725e3/pytest_httpx-0.36.0-py3-none-any.whl", hash = "sha256:bd4c120bb80e142df856e825ec9f17981effb84d159f9fa29ed97e2357c3a9c8", size = 20229, upload-time = "2025-12-02T16:34:56.45Z" }, -] - [[package]] name = "pytokens" version = "0.4.1" @@ -1245,6 +1275,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, ] +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + [[package]] name = "typeapi" version = "2.3.0" @@ -1328,20 +1367,20 @@ version = "10.3.0" source = { editable = "." } dependencies = [ { name = "cryptography" }, - { name = "httpx" }, + { name = "httpx2" }, { name = "pyjwt" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] [package.dev-dependencies] dev = [ + { name = "httpx" }, { name = "nox" }, { name = "nox-uv" }, { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, - { name = "pytest-httpx" }, { name = "ruff" }, ] docs = [ @@ -1357,32 +1396,33 @@ nox = [ { name = "nox-uv" }, ] test = [ + { name = "httpx" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, - { name = "pytest-httpx" }, ] type-check = [ + { name = "httpx" }, { name = "pyright" }, ] [package.metadata] requires-dist = [ { name = "cryptography", specifier = "~=50.0" }, - { name = "httpx", specifier = "~=0.28" }, + { name = "httpx2", specifier = "~=2.13" }, { name = "pyjwt", specifier = "~=2.12" }, { name = "typing-extensions", marker = "python_full_version < '3.11'", specifier = "~=4.0" }, ] [package.metadata.requires-dev] dev = [ + { name = "httpx", specifier = "~=0.28" }, { name = "nox", specifier = "~=2026.2" }, { name = "nox-uv", specifier = "~=0.7" }, { name = "pyright", specifier = "~=1.1" }, { name = "pytest", specifier = "~=9.0" }, { name = "pytest-asyncio", specifier = "~=1.3" }, { name = "pytest-cov", specifier = "~=7.1" }, - { name = "pytest-httpx", specifier = "~=0.36" }, { name = "ruff", specifier = "~=0.15" }, ] docs = [ @@ -1396,12 +1436,15 @@ nox = [ { name = "nox-uv", specifier = "~=0.7" }, ] test = [ + { name = "httpx", specifier = "~=0.28" }, { name = "pytest", specifier = "~=9.0" }, { name = "pytest-asyncio", specifier = "~=1.3" }, { name = "pytest-cov", specifier = "~=7.1" }, - { name = "pytest-httpx", specifier = "~=0.36" }, ] -type-check = [{ name = "pyright", specifier = "~=1.1" }] +type-check = [ + { name = "httpx", specifier = "~=0.28" }, + { name = "pyright", specifier = "~=1.1" }, +] [[package]] name = "wrapt" From 70987c71f56f2d2d6a3e94fe2605aba3f0f1c8e8 Mon Sep 17 00:00:00 2001 From: madison Date: Thu, 17 Sep 2026 09:42:46 -0700 Subject: [PATCH 7/9] feat: Add optional jwt_issuer option for session token validation (#725) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Garen J. Torikian --- README.md | 1 + src/workos/_base_client.py | 28 ++++- src/workos/session.py | 4 + tests/test_session.py | 225 ++++++++++++++++++++++++++++++++++++- 4 files changed, 256 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0d7da15ae..b2a7395f8 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ The client reads credentials from the environment when not passed explicitly: | `WORKOS_CLIENT_ID` | WorkOS client ID | | `WORKOS_BASE_URL` | Override the API base URL (defaults to `https://api.workos.com/`) | | `WORKOS_REQUEST_TIMEOUT` | HTTP timeout in seconds (defaults to `60`) | +| `WORKOS_ISSUER` | Expected `iss` claim of session access tokens, comma-separated to accept several (not validated when unset; also settable via `jwt_issuer=`) | ## Available Resources diff --git a/src/workos/_base_client.py b/src/workos/_base_client.py index 24a907d49..7c6f7e645 100644 --- a/src/workos/_base_client.py +++ b/src/workos/_base_client.py @@ -10,7 +10,7 @@ import random from datetime import datetime, timezone from email.utils import parsedate_to_datetime -from typing import Any, Dict, Optional, Sequence, Type, cast, overload +from typing import Any, Dict, List, Optional, Sequence, Type, Union, cast, overload from urllib.parse import quote, urlencode from ._errors import ( @@ -55,6 +55,14 @@ _INVALID_PATH_SEGMENTS = frozenset({"", ".", ".."}) +def _parse_issuer_env(value: str) -> Optional[Union[str, List[str]]]: + issuers = [issuer.strip() for issuer in value.split(",")] + issuers = [issuer for issuer in issuers if issuer] + if not issuers: + return None + return issuers[0] if len(issuers) == 1 else issuers + + class _BaseWorkOSClient: """Shared WorkOS client implementation.""" @@ -66,6 +74,7 @@ def __init__( base_url: Optional[str] = None, request_timeout: Optional[int] = None, jwt_leeway: float = 0.0, + jwt_issuer: Optional[Union[str, Sequence[str]]] = None, max_retries: int = MAX_RETRIES, is_public: bool = False, ) -> None: @@ -95,6 +104,13 @@ def __init__( ) self._max_retries = max_retries self._jwt_leeway = jwt_leeway + if jwt_issuer is None: + env_issuer = os.environ.get("WORKOS_ISSUER") + self._jwt_issuer: Optional[Union[str, Sequence[str]]] = ( + _parse_issuer_env(env_issuer) if env_issuer else None + ) + else: + self._jwt_issuer = jwt_issuer @property def base_url(self) -> str: @@ -439,6 +455,7 @@ def __init__( base_url: Optional[str] = None, request_timeout: Optional[int] = None, jwt_leeway: float = 0.0, + jwt_issuer: Optional[Union[str, Sequence[str]]] = None, max_retries: int = MAX_RETRIES, is_public: bool = False, http_client: Optional[SyncHTTPClient] = None, @@ -451,6 +468,9 @@ def __init__( base_url: Base URL for API requests. Falls back to WORKOS_BASE_URL or "https://api.workos.com". request_timeout: HTTP request timeout in seconds. Falls back to WORKOS_REQUEST_TIMEOUT or 60. jwt_leeway: JWT clock skew leeway in seconds. + jwt_issuer: Expected ``iss`` claim of session access tokens, or a list of + accepted issuers. Falls back to the WORKOS_ISSUER environment variable + (comma-separated for a list). When unset, the issuer is not validated. max_retries: Maximum number of retries for failed requests. Defaults to 3. is_public: When True, mark this client as public (PKCE / browser / mobile / CLI). The API key is forced to None and the @@ -472,6 +492,7 @@ def __init__( base_url=base_url, request_timeout=request_timeout, jwt_leeway=jwt_leeway, + jwt_issuer=jwt_issuer, max_retries=max_retries, is_public=is_public, ) @@ -681,6 +702,7 @@ def __init__( base_url: Optional[str] = None, request_timeout: Optional[int] = None, jwt_leeway: float = 0.0, + jwt_issuer: Optional[Union[str, Sequence[str]]] = None, max_retries: int = MAX_RETRIES, is_public: bool = False, http_client: Optional[AsyncHTTPClient] = None, @@ -693,6 +715,9 @@ def __init__( base_url: Base URL for API requests. Falls back to WORKOS_BASE_URL or "https://api.workos.com". request_timeout: HTTP request timeout in seconds. Falls back to WORKOS_REQUEST_TIMEOUT or 60. jwt_leeway: JWT clock skew leeway in seconds. + jwt_issuer: Expected ``iss`` claim of session access tokens, or a list of + accepted issuers. Falls back to the WORKOS_ISSUER environment variable + (comma-separated for a list). When unset, the issuer is not validated. max_retries: Maximum number of retries for failed requests. Defaults to 3. http_client: HTTP client to send requests with. Accepts an ``httpx2.AsyncClient``, an ``httpx.AsyncClient``, or any object @@ -710,6 +735,7 @@ def __init__( base_url=base_url, request_timeout=request_timeout, jwt_leeway=jwt_leeway, + jwt_issuer=jwt_issuer, max_retries=max_retries, is_public=is_public, ) diff --git a/src/workos/session.py b/src/workos/session.py index 236a250c9..92c31d4bb 100644 --- a/src/workos/session.py +++ b/src/workos/session.py @@ -289,6 +289,7 @@ def authenticate( algorithms=self._JWK_ALGORITHMS, options={"verify_aud": False}, leeway=self._client._jwt_leeway, + issuer=self._client._jwt_issuer, ) except jwt.exceptions.InvalidTokenError: return AuthenticateWithSessionCookieErrorResponse( @@ -370,6 +371,7 @@ def refresh( algorithms=self._JWK_ALGORITHMS, options={"verify_aud": False}, leeway=self._client._jwt_leeway, + issuer=self._client._jwt_issuer, ) except ( jwt.exceptions.InvalidTokenError, @@ -497,6 +499,7 @@ def authenticate( algorithms=self._JWK_ALGORITHMS, options={"verify_aud": False}, leeway=self._client._jwt_leeway, + issuer=self._client._jwt_issuer, ) except jwt.exceptions.InvalidTokenError: return AuthenticateWithSessionCookieErrorResponse( @@ -578,6 +581,7 @@ async def refresh( algorithms=self._JWK_ALGORITHMS, options={"verify_aud": False}, leeway=self._client._jwt_leeway, + issuer=self._client._jwt_issuer, ) except ( jwt.exceptions.InvalidTokenError, diff --git a/tests/test_session.py b/tests/test_session.py index a13600291..9d0bd3707 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -6,7 +6,7 @@ from cryptography.fernet import Fernet, InvalidToken from cryptography.hazmat.primitives.asymmetric import rsa -from workos import WorkOSClient +from workos import AsyncWorkOSClient, WorkOSClient from workos._errors import ( AuthenticationError, AuthenticationMethodNotAllowedError, @@ -210,6 +210,165 @@ def test_session_authenticate_expired_jwt(self): assert isinstance(result, AuthenticateWithSessionCookieErrorResponse) assert result.reason == AuthenticateWithSessionCookieFailureReason.INVALID_JWT + def _authenticate_with_client(self, client, claims=None): + token = _make_jwt(self.private_key, claims=claims) + sealed = self._make_sealed_session(access_token=token) + session = Session( + client=client, session_data=sealed, cookie_password=COOKIE_PASSWORD + ) + session.jwks = self._mock_jwks() + return session.authenticate() + + def test_session_authenticate_ignores_issuer_when_not_configured(self): + result = self._authenticate_with_client( + self.workos, claims={"iss": "https://other.example.com"} + ) + assert isinstance(result, AuthenticateWithSessionCookieSuccessResponse) + + def test_session_authenticate_accepts_configured_issuer(self): + client = WorkOSClient( + api_key="sk_test_123", + client_id="client_test_123", + jwt_issuer="https://api.workos.com/user_management/client_test_123", + max_retries=0, + ) + try: + result = self._authenticate_with_client( + client, + claims={ + "iss": "https://api.workos.com/user_management/client_test_123" + }, + ) + finally: + client.close() + assert isinstance(result, AuthenticateWithSessionCookieSuccessResponse) + + def test_session_authenticate_rejects_mismatched_issuer(self): + client = WorkOSClient( + api_key="sk_test_123", + client_id="client_test_123", + jwt_issuer="https://api.workos.com", + max_retries=0, + ) + try: + result = self._authenticate_with_client( + client, claims={"iss": "https://other.example.com"} + ) + finally: + client.close() + assert isinstance(result, AuthenticateWithSessionCookieErrorResponse) + assert result.reason == AuthenticateWithSessionCookieFailureReason.INVALID_JWT + + def test_session_authenticate_accepts_any_listed_issuer(self): + client = WorkOSClient( + api_key="sk_test_123", + client_id="client_test_123", + jwt_issuer=["https://api.workos.com", "https://auth.example.com"], + max_retries=0, + ) + try: + result = self._authenticate_with_client( + client, claims={"iss": "https://auth.example.com"} + ) + finally: + client.close() + assert isinstance(result, AuthenticateWithSessionCookieSuccessResponse) + + def test_session_authenticate_reads_issuer_list_from_env(self, monkeypatch): + monkeypatch.setenv( + "WORKOS_ISSUER", "https://api.workos.com, https://auth.example.com," + ) + client = WorkOSClient( + api_key="sk_test_123", client_id="client_test_123", max_retries=0 + ) + try: + assert client._jwt_issuer == [ + "https://api.workos.com", + "https://auth.example.com", + ] + result = self._authenticate_with_client( + client, claims={"iss": "https://auth.example.com"} + ) + finally: + client.close() + assert isinstance(result, AuthenticateWithSessionCookieSuccessResponse) + + def test_session_empty_issuer_list_rejects_all_tokens(self): + client = WorkOSClient( + api_key="sk_test_123", + client_id="client_test_123", + jwt_issuer=[], + max_retries=0, + ) + try: + assert client._jwt_issuer == [] + result = self._authenticate_with_client( + client, claims={"iss": "https://api.workos.com"} + ) + finally: + client.close() + assert isinstance(result, AuthenticateWithSessionCookieErrorResponse) + assert result.reason == AuthenticateWithSessionCookieFailureReason.INVALID_JWT + + def _refresh_with_client(self, client, claims=None): + new_token = _make_jwt(self.private_key, claims=claims) + sealed = seal_data( + {"refresh_token": "rt_old", "user": {"id": "user_01"}}, COOKIE_PASSWORD + ) + session = Session( + client=client, session_data=sealed, cookie_password=COOKIE_PASSWORD + ) + session.jwks = self._mock_jwks() + session._client.request_raw = MagicMock( + return_value={ + "access_token": new_token, + "refresh_token": "rt_new", + "user": {"id": "user_01"}, + } + ) + return session.refresh() + + def test_session_refresh_accepts_configured_issuer(self): + client = WorkOSClient( + api_key="sk_test_123", + client_id="client_test_123", + jwt_issuer="https://api.workos.com", + max_retries=0, + ) + try: + result = self._refresh_with_client( + client, claims={"iss": "https://api.workos.com"} + ) + finally: + client.close() + assert isinstance(result, RefreshWithSessionCookieSuccessResponse) + + def test_session_refresh_rejects_mismatched_issuer(self): + client = WorkOSClient( + api_key="sk_test_123", + client_id="client_test_123", + jwt_issuer="https://api.workos.com", + max_retries=0, + ) + try: + result = self._refresh_with_client( + client, claims={"iss": "https://other.example.com"} + ) + finally: + client.close() + assert isinstance(result, RefreshWithSessionCookieErrorResponse) + assert result.reason == AuthenticateWithSessionCookieFailureReason.INVALID_JWT + + def test_session_single_env_issuer_stays_string(self, monkeypatch): + monkeypatch.setenv("WORKOS_ISSUER", "https://api.workos.com") + client = WorkOSClient( + api_key="sk_test_123", client_id="client_test_123", max_retries=0 + ) + try: + assert client._jwt_issuer == "https://api.workos.com" + finally: + client.close() + def test_session_refresh_invalid_session(self): session = Session( client=self.workos, session_data="garbage", cookie_password=COOKIE_PASSWORD @@ -581,6 +740,70 @@ async def test_async_session_authenticate_success(self, async_workos): assert isinstance(result, AuthenticateWithSessionCookieSuccessResponse) assert result.session_id == "session_01" + async def test_async_session_authenticate_rejects_mismatched_issuer(self): + private_key, public_key = _generate_rsa_key_pair() + token = _make_jwt(private_key, claims={"iss": "https://other.example.com"}) + sealed = seal_data( + {"access_token": token, "refresh_token": "rt", "user": {"id": "u1"}}, + COOKIE_PASSWORD, + ) + client = AsyncWorkOSClient( + api_key="sk_test_123", + client_id="client_test_123", + jwt_issuer=["https://api.workos.com", "https://auth.example.com"], + ) + try: + session = AsyncSession( + client=client, session_data=sealed, cookie_password=COOKIE_PASSWORD + ) + session.jwks = self._mock_jwks(public_key) + result = session.authenticate() + finally: + await client.close() + assert isinstance(result, AuthenticateWithSessionCookieErrorResponse) + assert result.reason == AuthenticateWithSessionCookieFailureReason.INVALID_JWT + + async def _refresh_with_issuer(self, jwt_issuer, iss): + from unittest.mock import AsyncMock + + private_key, public_key = _generate_rsa_key_pair() + new_token = _make_jwt(private_key, claims={"iss": iss}) + sealed = seal_data( + {"refresh_token": "rt_old", "user": {"id": "user_01"}}, COOKIE_PASSWORD + ) + client = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test_123", jwt_issuer=jwt_issuer + ) + try: + session = AsyncSession( + client=client, session_data=sealed, cookie_password=COOKIE_PASSWORD + ) + session.jwks = self._mock_jwks(public_key) + session._client.request_raw = AsyncMock( + return_value={ + "access_token": new_token, + "refresh_token": "rt_new", + "user": {"id": "user_01"}, + } + ) + return await session.refresh() + finally: + await client.close() + + async def test_async_session_refresh_accepts_configured_issuer(self): + result = await self._refresh_with_issuer( + ["https://api.workos.com", "https://auth.example.com"], + "https://auth.example.com", + ) + assert isinstance(result, RefreshWithSessionCookieSuccessResponse) + + async def test_async_session_refresh_rejects_mismatched_issuer(self): + result = await self._refresh_with_issuer( + "https://api.workos.com", "https://other.example.com" + ) + assert isinstance(result, RefreshWithSessionCookieErrorResponse) + assert result.reason == AuthenticateWithSessionCookieFailureReason.INVALID_JWT + async def test_async_session_refresh_success(self, async_workos): from unittest.mock import AsyncMock From abf5dc24c2ef2dcebd1359567ab45970b09cd581 Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:56:52 +0000 Subject: [PATCH 8/9] fix(generated): Move organization connected-account operations onto Pipes (#733) Co-authored-by: workos-sdk-automation[bot] <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Co-authored-by: Garen Torikian --- ...49c30cb87778129af801fadc33546518853d045.md | 3 +- ...fe7aedb3cf7296ffb6ead1e287e844186479702.md | 5 + .oagen-manifest.json | 408 ++++++------ src/workos/_client.py | 14 - src/workos/common/__init__.py | 1 - src/workos/common/models/__init__.py | 74 --- .../organizations_data_providers/__init__.py | 5 - .../organizations_data_providers/_resource.py | 584 ------------------ .../models/__init__.py | 12 - src/workos/pipes/_resource.py | 552 +++++++++++++++++ src/workos/pipes/models/__init__.py | 16 +- .../pipes/models/connected_account_input.py | 64 -- .../models/data_integrations_list_response.py | 43 -- .../data_integrations_list_response_data.py | 126 ---- ...ns_list_response_data_connected_account.py | 134 ---- .../organizations_data_providers/__init__.py | 3 - tests/test_organizations_data_providers.py | 501 --------------- ...ations_data_providers_models_round_trip.py | 441 ------------- tests/test_pipes.py | 283 +++++++++ tests/test_pipes_models_round_trip.py | 432 +++++++++++++ 20 files changed, 1485 insertions(+), 2216 deletions(-) create mode 100644 .changelog-pending/2026-09-17T20-59-47-bfe7aedb3cf7296ffb6ead1e287e844186479702.md delete mode 100644 src/workos/organizations_data_providers/_resource.py delete mode 100644 src/workos/pipes/models/connected_account_input.py delete mode 100644 src/workos/pipes/models/data_integrations_list_response.py delete mode 100644 src/workos/pipes/models/data_integrations_list_response_data.py delete mode 100644 src/workos/pipes/models/data_integrations_list_response_data_connected_account.py delete mode 100644 src/workos/types/organizations_data_providers/__init__.py delete mode 100644 tests/test_organizations_data_providers.py delete mode 100644 tests/test_organizations_data_providers_models_round_trip.py diff --git a/.changelog-pending/2026-09-16T19-24-23-949c30cb87778129af801fadc33546518853d045.md b/.changelog-pending/2026-09-16T19-24-23-949c30cb87778129af801fadc33546518853d045.md index 744a55f68..fbb6a9e36 100644 --- a/.changelog-pending/2026-09-16T19-24-23-949c30cb87778129af801fadc33546518853d045.md +++ b/.changelog-pending/2026-09-16T19-24-23-949c30cb87778129af801fadc33546518853d045.md @@ -16,13 +16,12 @@ * Added enum `DataIntegrationInstallationConnectionRole` * Added `connection_owner` to `DataIntegrationsGetDataIntegrationAuthorizeUrlRequest` * Added enum `DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner` + * Added the organization connected-account operations on `Pipes`: `client.pipes.get_organization_connected_account`, `create_organization_connected_account`, `update_organization_connected_account`, `delete_organization_connected_account`, `list_organization_data_providers` * **[connect](https://workos.com/docs/reference/workos-connect/standalone)**: * Added `connection_role` to `ConnectedAccount` * Added `account_identifier` to `ConnectedAccount` * Added `account_display_name` to `ConnectedAccount` * Added enum `ConnectedAccountConnectionRole` - * **[organizations](https://workos.com/docs/reference/organization)**: - * Added service `OrganizationsDataProviders` * **[webhooks](https://workos.com/docs/reference/webhooks)**: * Added `pipes.account_connection.add_failed` to `CreateWebhookEndpointEvents` * Added `pipes.account_connection.connected` to `CreateWebhookEndpointEvents` diff --git a/.changelog-pending/2026-09-17T20-59-47-bfe7aedb3cf7296ffb6ead1e287e844186479702.md b/.changelog-pending/2026-09-17T20-59-47-bfe7aedb3cf7296ffb6ead1e287e844186479702.md new file mode 100644 index 000000000..b1e228daf --- /dev/null +++ b/.changelog-pending/2026-09-17T20-59-47-bfe7aedb3cf7296ffb6ead1e287e844186479702.md @@ -0,0 +1,5 @@ +* [#733](https://github.com/workos/workos-python/pull/733) fix(generated): regenerate from spec (1 change) + + **Fixes** + * **[multi_factor_auth](https://workos.com/docs/reference/authkit/mfa)**: + * Changed errors for endpoint `POST /auth/factors/{id}/challenge` diff --git a/.oagen-manifest.json b/.oagen-manifest.json index 056fa1e7f..2f027a66f 100644 --- a/.oagen-manifest.json +++ b/.oagen-manifest.json @@ -691,13 +691,11 @@ "src/workos/organizations/models/update_audit_logs_retention.py", "src/workos/organizations/models/update_organization.py", "src/workos/organizations_data_providers/__init__.py", - "src/workos/organizations_data_providers/_resource.py", "src/workos/organizations_data_providers/models/__init__.py", "src/workos/pipes/__init__.py", "src/workos/pipes/_resource.py", "src/workos/pipes/models/__init__.py", "src/workos/pipes/models/api_key_installation.py", - "src/workos/pipes/models/connected_account_input.py", "src/workos/pipes/models/create_data_integration.py", "src/workos/pipes/models/custom_provider_definition.py", "src/workos/pipes/models/data_integration.py", @@ -712,9 +710,6 @@ "src/workos/pipes/models/data_integration_installation.py", "src/workos/pipes/models/data_integrations_get_data_integration_authorize_url_request.py", "src/workos/pipes/models/data_integrations_get_user_token_request.py", - "src/workos/pipes/models/data_integrations_list_response.py", - "src/workos/pipes/models/data_integrations_list_response_data.py", - "src/workos/pipes/models/data_integrations_list_response_data_connected_account.py", "src/workos/pipes/models/data_integrations_upsert_api_key_request.py", "src/workos/pipes/models/data_integrations_upsert_client_credentials_request.py", "src/workos/pipes/models/data_integrations_vend_credentials_request.py", @@ -792,7 +787,6 @@ "src/workos/types/organization_domains/__init__.py", "src/workos/types/organization_membership/__init__.py", "src/workos/types/organizations/__init__.py", - "src/workos/types/organizations_data_providers/__init__.py", "src/workos/types/pipes/__init__.py", "src/workos/types/pipes_provider/__init__.py", "src/workos/types/platform_teams/__init__.py", @@ -1573,8 +1567,6 @@ "tests/test_organization_membership.py", "tests/test_organization_membership_models_round_trip.py", "tests/test_organizations.py", - "tests/test_organizations_data_providers.py", - "tests/test_organizations_data_providers_models_round_trip.py", "tests/test_organizations_models_round_trip.py", "tests/test_pipes.py", "tests/test_pipes_models_round_trip.py", @@ -1596,6 +1588,34 @@ "tests/test_widgets_models_round_trip.py" ], "operations": { + "GET /agents/blueprints": { + "sdkMethod": "list_blueprints", + "service": "agents" + }, + "POST /agents/blueprints": { + "sdkMethod": "create_blueprint", + "service": "agents" + }, + "GET /agents/blueprints/{agent_blueprint_id}": { + "sdkMethod": "get_blueprint", + "service": "agents" + }, + "PATCH /agents/blueprints/{agent_blueprint_id}": { + "sdkMethod": "update_blueprint", + "service": "agents" + }, + "DELETE /agents/blueprints/{agent_blueprint_id}": { + "sdkMethod": "delete_blueprint", + "service": "agents" + }, + "POST /agents/blueprints/{agent_blueprint_id}/tokens": { + "sdkMethod": "create_blueprint_token", + "service": "agents" + }, + "POST /agents/blueprints/{agent_blueprint_id}/tokens/validate": { + "sdkMethod": "validate_blueprint_token", + "service": "agents" + }, "PATCH /agents/claims/attempts": { "sdkMethod": "update_attempts", "service": "agents" @@ -1608,6 +1628,30 @@ "sdkMethod": "get_registration", "service": "agents" }, + "GET /agents/instances": { + "sdkMethod": "list_instances", + "service": "agents" + }, + "GET /agents/instances/{agent_instance_id}": { + "sdkMethod": "get_instance", + "service": "agents" + }, + "DELETE /agents/instances/{agent_instance_id}": { + "sdkMethod": "delete_instance", + "service": "agents" + }, + "GET /agents/sessions": { + "sdkMethod": "list_sessions", + "service": "agents" + }, + "GET /agents/sessions/{agent_instance_session_id}": { + "sdkMethod": "get_session", + "service": "agents" + }, + "POST /agents/sessions/{agent_instance_session_id}/revoke": { + "sdkMethod": "revoke_session", + "service": "agents" + }, "POST /auth/challenges/{id}/verify": { "sdkMethod": "verify_challenge", "service": "multi_factor_auth" @@ -1852,10 +1896,54 @@ "sdkMethod": "list_connections", "service": "sso" }, + "POST /connections": { + "sdkMethod": "create_connection", + "service": "sso" + }, + "GET /connections/{connectionId}/saml_idp_signing_certs": { + "sdkMethod": "list_connection_saml_idp_signing_certs", + "service": "sso" + }, + "POST /connections/{connectionId}/saml_idp_signing_certs": { + "sdkMethod": "create_connection_saml_idp_signing_cert", + "service": "sso" + }, + "DELETE /connections/{connectionId}/saml_idp_signing_certs/{certificateId}": { + "sdkMethod": "delete_connection_saml_idp_signing_cert", + "service": "sso" + }, + "GET /connections/{connectionId}/saml_sp_encryption_certs": { + "sdkMethod": "list_connection_saml_sp_encryption_certs", + "service": "sso" + }, + "POST /connections/{connectionId}/saml_sp_encryption_certs": { + "sdkMethod": "create_connection_saml_sp_encryption_cert", + "service": "sso" + }, + "DELETE /connections/{connectionId}/saml_sp_encryption_certs/{certificateId}": { + "sdkMethod": "delete_connection_saml_sp_encryption_cert", + "service": "sso" + }, + "GET /connections/{connectionId}/saml_sp_signing_cert": { + "sdkMethod": "list_connection_saml_sp_signing_cert", + "service": "sso" + }, + "POST /connections/{connectionId}/saml_sp_signing_cert": { + "sdkMethod": "create_connection_saml_sp_signing_cert", + "service": "sso" + }, + "DELETE /connections/{connectionId}/saml_sp_signing_cert/{certificateId}": { + "sdkMethod": "delete_connection_saml_sp_signing_cert", + "service": "sso" + }, "GET /connections/{id}": { "sdkMethod": "get_connection", "service": "sso" }, + "PATCH /connections/{id}": { + "sdkMethod": "update_connection", + "service": "sso" + }, "DELETE /connections/{id}": { "sdkMethod": "delete_connection", "service": "sso" @@ -1888,10 +1976,26 @@ "sdkMethod": "authorize_data_integration", "service": "pipes" }, + "PUT /data-integrations/{slug}/client-credentials": { + "sdkMethod": "update_data_integration_client_credentials", + "service": "pipes" + }, "POST /data-integrations/{slug}/credentials": { "sdkMethod": "create_data_integration_credential", "service": "pipes" }, + "GET /data-integrations/{slug}/organization": { + "sdkMethod": "list_data_integration_organization", + "service": "pipes" + }, + "PUT /data-integrations/{slug}/organization": { + "sdkMethod": "update_data_integration_organization", + "service": "pipes" + }, + "DELETE /data-integrations/{slug}/organization": { + "sdkMethod": "delete_data_integration_organization", + "service": "pipes" + }, "POST /data-integrations/{provider}/token": { "sdkMethod": "get_access_token", "service": "pipes" @@ -2060,6 +2164,54 @@ "sdkMethod": "list_authorized_applications", "service": "organizations" }, + "GET /organizations/{organization_id}/connected_accounts/{slug}": { + "sdkMethod": "get_organization_connected_account", + "service": "pipes" + }, + "POST /organizations/{organization_id}/connected_accounts/{slug}": { + "sdkMethod": "create_organization_connected_account", + "service": "pipes" + }, + "PUT /organizations/{organization_id}/connected_accounts/{slug}": { + "sdkMethod": "update_organization_connected_account", + "service": "pipes" + }, + "DELETE /organizations/{organization_id}/connected_accounts/{slug}": { + "sdkMethod": "delete_organization_connected_account", + "service": "pipes" + }, + "GET /organizations/{organization_id}/data_providers": { + "sdkMethod": "list_organization_data_providers", + "service": "pipes" + }, + "GET /organizations/{organization_id}/it_contacts": { + "sdkMethod": "list_it_contacts", + "service": "organizations" + }, + "POST /organizations/{organization_id}/it_contacts": { + "sdkMethod": "create_it_contact", + "service": "organizations" + }, + "DELETE /organizations/{organization_id}/it_contacts/{contact_id}": { + "sdkMethod": "delete_it_contact", + "service": "organizations" + }, + "POST /organizations/{organization_id}/it_contacts/{contact_id}/invite": { + "sdkMethod": "invite_it_contact", + "service": "organizations" + }, + "POST /organizations/{organization_id}/it_contacts/{contact_id}/revoke": { + "sdkMethod": "revoke_it_contact", + "service": "organizations" + }, + "POST /platform/teams": { + "sdkMethod": "create_team", + "service": "platform_teams" + }, + "GET /platform/teams/{team_id}": { + "sdkMethod": "get_team", + "service": "platform_teams" + }, "POST /portal/generate_link": { "sdkMethod": "generate_link", "service": "admin_portal" @@ -2132,6 +2284,18 @@ "sdkMethod": "revoke_session", "service": "user_management" }, + "GET /user_management/authkit_oauth_resources": { + "sdkMethod": "list_authkit_oauth_resources", + "service": "user_management" + }, + "POST /user_management/authkit_oauth_resources": { + "sdkMethod": "create_authkit_oauth_resource", + "service": "user_management" + }, + "DELETE /user_management/authkit_oauth_resources/{id}": { + "sdkMethod": "delete_authkit_oauth_resource", + "service": "user_management" + }, "GET /user_management/cors_origins": { "sdkMethod": "list_cors_origins", "service": "user_management" @@ -2332,6 +2496,34 @@ "sdkMethod": "create_user_auth_factor", "service": "multi_factor_auth" }, + "DELETE /user_management/waitlist_entries/{id}": { + "sdkMethod": "delete_waitlist_entry", + "service": "user_management" + }, + "POST /user_management/waitlist_entries/{id}/approve": { + "sdkMethod": "create_waitlist_entry_approve", + "service": "user_management" + }, + "POST /user_management/waitlist_entries/{id}/deny": { + "sdkMethod": "create_waitlist_entry_deny", + "service": "user_management" + }, + "GET /user_management/waitlists": { + "sdkMethod": "list_waitlists", + "service": "user_management" + }, + "GET /user_management/waitlists/{id}": { + "sdkMethod": "get_waitlist", + "service": "user_management" + }, + "GET /user_management/waitlists/{id}/entries": { + "sdkMethod": "list_waitlist_entries", + "service": "user_management" + }, + "POST /user_management/waitlists/{id}/entries": { + "sdkMethod": "create_waitlist_entry", + "service": "user_management" + }, "POST /vault/v1/keys/data-key": { "sdkMethod": "create_data_key", "service": "vault" @@ -2439,206 +2631,6 @@ "GET /audit_logs/exports/{auditLogExportId}": { "sdkMethod": "get_export", "service": "audit_logs" - }, - "PUT /data-integrations/{slug}/client-credentials": { - "sdkMethod": "update_data_integration_client_credentials", - "service": "pipes" - }, - "GET /agents/blueprints": { - "sdkMethod": "list_blueprints", - "service": "agents" - }, - "POST /agents/blueprints": { - "sdkMethod": "create_blueprint", - "service": "agents" - }, - "GET /agents/blueprints/{agent_blueprint_id}": { - "sdkMethod": "get_blueprint", - "service": "agents" - }, - "PATCH /agents/blueprints/{agent_blueprint_id}": { - "sdkMethod": "update_blueprint", - "service": "agents" - }, - "DELETE /agents/blueprints/{agent_blueprint_id}": { - "sdkMethod": "delete_blueprint", - "service": "agents" - }, - "POST /agents/blueprints/{agent_blueprint_id}/tokens": { - "sdkMethod": "create_blueprint_token", - "service": "agents" - }, - "GET /agents/instances": { - "sdkMethod": "list_instances", - "service": "agents" - }, - "GET /agents/instances/{agent_instance_id}": { - "sdkMethod": "get_instance", - "service": "agents" - }, - "DELETE /agents/instances/{agent_instance_id}": { - "sdkMethod": "delete_instance", - "service": "agents" - }, - "GET /agents/sessions": { - "sdkMethod": "list_sessions", - "service": "agents" - }, - "GET /agents/sessions/{agent_instance_session_id}": { - "sdkMethod": "get_session", - "service": "agents" - }, - "POST /agents/sessions/{agent_instance_session_id}/revoke": { - "sdkMethod": "revoke_session", - "service": "agents" - }, - "POST /connections": { - "sdkMethod": "create_connection", - "service": "sso" - }, - "GET /connections/{connectionId}/saml_idp_signing_certs": { - "sdkMethod": "list_connection_saml_idp_signing_certs", - "service": "sso" - }, - "POST /connections/{connectionId}/saml_idp_signing_certs": { - "sdkMethod": "create_connection_saml_idp_signing_cert", - "service": "sso" - }, - "DELETE /connections/{connectionId}/saml_idp_signing_certs/{certificateId}": { - "sdkMethod": "delete_connection_saml_idp_signing_cert", - "service": "sso" - }, - "GET /connections/{connectionId}/saml_sp_encryption_certs": { - "sdkMethod": "list_connection_saml_sp_encryption_certs", - "service": "sso" - }, - "POST /connections/{connectionId}/saml_sp_encryption_certs": { - "sdkMethod": "create_connection_saml_sp_encryption_cert", - "service": "sso" - }, - "DELETE /connections/{connectionId}/saml_sp_encryption_certs/{certificateId}": { - "sdkMethod": "delete_connection_saml_sp_encryption_cert", - "service": "sso" - }, - "GET /connections/{connectionId}/saml_sp_signing_cert": { - "sdkMethod": "list_connection_saml_sp_signing_cert", - "service": "sso" - }, - "POST /connections/{connectionId}/saml_sp_signing_cert": { - "sdkMethod": "create_connection_saml_sp_signing_cert", - "service": "sso" - }, - "DELETE /connections/{connectionId}/saml_sp_signing_cert/{certificateId}": { - "sdkMethod": "delete_connection_saml_sp_signing_cert", - "service": "sso" - }, - "PATCH /connections/{id}": { - "sdkMethod": "update_connection", - "service": "sso" - }, - "GET /organizations/{organization_id}/it_contacts": { - "sdkMethod": "list_it_contacts", - "service": "organizations" - }, - "POST /organizations/{organization_id}/it_contacts": { - "sdkMethod": "create_it_contact", - "service": "organizations" - }, - "DELETE /organizations/{organization_id}/it_contacts/{contact_id}": { - "sdkMethod": "delete_it_contact", - "service": "organizations" - }, - "POST /organizations/{organization_id}/it_contacts/{contact_id}/invite": { - "sdkMethod": "invite_it_contact", - "service": "organizations" - }, - "POST /organizations/{organization_id}/it_contacts/{contact_id}/revoke": { - "sdkMethod": "revoke_it_contact", - "service": "organizations" - }, - "POST /platform/teams": { - "sdkMethod": "create_team", - "service": "platform_teams" - }, - "GET /platform/teams/{team_id}": { - "sdkMethod": "get_team", - "service": "platform_teams" - }, - "DELETE /user_management/waitlist_entries/{id}": { - "sdkMethod": "delete_waitlist_entry", - "service": "user_management" - }, - "POST /user_management/waitlist_entries/{id}/approve": { - "sdkMethod": "create_waitlist_entry_approve", - "service": "user_management" - }, - "POST /user_management/waitlist_entries/{id}/deny": { - "sdkMethod": "create_waitlist_entry_deny", - "service": "user_management" - }, - "GET /user_management/waitlists": { - "sdkMethod": "list_waitlists", - "service": "user_management" - }, - "GET /user_management/waitlists/{id}": { - "sdkMethod": "get_waitlist", - "service": "user_management" - }, - "GET /user_management/waitlists/{id}/entries": { - "sdkMethod": "list_waitlist_entries", - "service": "user_management" - }, - "POST /user_management/waitlists/{id}/entries": { - "sdkMethod": "create_waitlist_entry", - "service": "user_management" - }, - "POST /agents/blueprints/{agent_blueprint_id}/tokens/validate": { - "sdkMethod": "validate_blueprint_token", - "service": "agents" - }, - "GET /data-integrations/{slug}/organization": { - "sdkMethod": "list_data_integration_organization", - "service": "pipes" - }, - "PUT /data-integrations/{slug}/organization": { - "sdkMethod": "update_data_integration_organization", - "service": "pipes" - }, - "DELETE /data-integrations/{slug}/organization": { - "sdkMethod": "delete_data_integration_organization", - "service": "pipes" - }, - "GET /user_management/authkit_oauth_resources": { - "sdkMethod": "list_authkit_oauth_resources", - "service": "user_management" - }, - "POST /user_management/authkit_oauth_resources": { - "sdkMethod": "create_authkit_oauth_resource", - "service": "user_management" - }, - "DELETE /user_management/authkit_oauth_resources/{id}": { - "sdkMethod": "delete_authkit_oauth_resource", - "service": "user_management" - }, - "GET /organizations/{organization_id}/connected_accounts/{slug}": { - "sdkMethod": "get_organization_connected_account", - "service": "organizations_data_providers" - }, - "POST /organizations/{organization_id}/connected_accounts/{slug}": { - "sdkMethod": "create_organization_connected_account", - "service": "organizations_data_providers" - }, - "PUT /organizations/{organization_id}/connected_accounts/{slug}": { - "sdkMethod": "update_organization_connected_account", - "service": "organizations_data_providers" - }, - "DELETE /organizations/{organization_id}/connected_accounts/{slug}": { - "sdkMethod": "delete_organization_connected_account", - "service": "organizations_data_providers" - }, - "GET /organizations/{organization_id}/data_providers": { - "sdkMethod": "list_organization_data_providers", - "service": "organizations_data_providers" } } } diff --git a/src/workos/_client.py b/src/workos/_client.py index 035d16f67..9776dd230 100644 --- a/src/workos/_client.py +++ b/src/workos/_client.py @@ -32,10 +32,6 @@ OrganizationMembershipService, ) from .organizations._resource import AsyncOrganizations, Organizations -from .organizations_data_providers._resource import ( - AsyncOrganizationsDataProviders, - OrganizationsDataProviders, -) from .passwordless import AsyncPasswordless, Passwordless from .pipes._resource import AsyncPipes, Pipes from .pipes_provider._resource import AsyncPipesProvider, PipesProvider @@ -127,11 +123,6 @@ def groups(self) -> Groups: """Groups API resources.""" return Groups(self) - @functools.cached_property - def organizations_data_providers(self) -> OrganizationsDataProviders: - """Organizations Data Providers API resources.""" - return OrganizationsDataProviders(self) - @functools.cached_property def platform_teams(self) -> PlatformTeams: """Platform Teams API resources.""" @@ -280,11 +271,6 @@ def groups(self) -> AsyncGroups: """Groups API resources.""" return AsyncGroups(self) - @functools.cached_property - def organizations_data_providers(self) -> AsyncOrganizationsDataProviders: - """Organizations Data Providers API resources.""" - return AsyncOrganizationsDataProviders(self) - @functools.cached_property def platform_teams(self) -> AsyncPlatformTeams: """Platform Teams API resources.""" diff --git a/src/workos/common/__init__.py b/src/workos/common/__init__.py index 3d04b3251..a8aff35c9 100644 --- a/src/workos/common/__init__.py +++ b/src/workos/common/__init__.py @@ -1,6 +1,5 @@ # This file is auto-generated by oagen. Do not edit. -from .models import * from .models import ( AccessTokenAgentRegistrationCredentialIssuedDataDetail as AccessTokenAgentRegistrationCredentialIssuedDataDetail, ) diff --git a/src/workos/common/models/__init__.py b/src/workos/common/models/__init__.py index 7aa3f369f..688edaf89 100644 --- a/src/workos/common/models/__init__.py +++ b/src/workos/common/models/__init__.py @@ -54,12 +54,10 @@ from .agent_instance_created_data import ( AgentInstanceCreatedData as AgentInstanceCreatedData, ) -from .agent_instance_created_data_type import * from .agent_instance_deleted import AgentInstanceDeleted as AgentInstanceDeleted from .agent_instance_deleted_data import ( AgentInstanceDeletedData as AgentInstanceDeletedData, ) -from .agent_instance_deleted_data_type import * from .agent_instance_session_created import ( AgentInstanceSessionCreated as AgentInstanceSessionCreated, ) @@ -100,9 +98,6 @@ from .agent_registration_created_data_agent_identity import ( AgentRegistrationCreatedDataAgentIdentity as AgentRegistrationCreatedDataAgentIdentity, ) -from .agent_registration_created_data_kind import * -from .agent_registration_created_data_method import * -from .agent_registration_created_data_status import * from .agent_registration_credential_issued import ( AgentRegistrationCredentialIssued as AgentRegistrationCredentialIssued, ) @@ -299,7 +294,6 @@ from .authentication_radar_risk_detected_data import ( AuthenticationRadarRiskDetectedData as AuthenticationRadarRiskDetectedData, ) -from .authentication_radar_risk_detected_data_action import * from .authentication_reauthentication_succeeded import ( AuthenticationReauthenticationSucceeded as AuthenticationReauthenticationSucceeded, ) @@ -378,26 +372,18 @@ from .connection_activated_data import ( ConnectionActivatedData as ConnectionActivatedData, ) -from .connection_activated_data_connection_type import * from .connection_activated_data_domain import ( ConnectionActivatedDataDomain as ConnectionActivatedDataDomain, ) -from .connection_activated_data_state import * -from .connection_activated_data_status import * from .connection_deactivated import ConnectionDeactivated as ConnectionDeactivated from .connection_deactivated_data import ( ConnectionDeactivatedData as ConnectionDeactivatedData, ) -from .connection_deactivated_data_connection_type import * from .connection_deactivated_data_domain import ( ConnectionDeactivatedDataDomain as ConnectionDeactivatedDataDomain, ) -from .connection_deactivated_data_state import * -from .connection_deactivated_data_status import * from .connection_deleted import ConnectionDeleted as ConnectionDeleted from .connection_deleted_data import ConnectionDeletedData as ConnectionDeletedData -from .connection_deleted_data_connection_type import * -from .connection_deleted_data_state import * from .connection_saml_certificate_renewal_required import ( ConnectionSAMLCertificateRenewalRequired as ConnectionSAMLCertificateRenewalRequired, ) @@ -407,7 +393,6 @@ from .connection_saml_certificate_renewal_required_data_certificate import ( ConnectionSAMLCertificateRenewalRequiredDataCertificate as ConnectionSAMLCertificateRenewalRequiredDataCertificate, ) -from .connection_saml_certificate_renewal_required_data_certificate_certificate_type import * from .connection_saml_certificate_renewal_required_data_connection import ( ConnectionSAMLCertificateRenewalRequiredDataConnection as ConnectionSAMLCertificateRenewalRequiredDataConnection, ) @@ -420,7 +405,6 @@ from .connection_saml_certificate_renewed_data_certificate import ( ConnectionSAMLCertificateRenewedDataCertificate as ConnectionSAMLCertificateRenewedDataCertificate, ) -from .connection_saml_certificate_renewed_data_certificate_certificate_type import * from .connection_saml_certificate_renewed_data_connection import ( ConnectionSAMLCertificateRenewedDataConnection as ConnectionSAMLCertificateRenewedDataConnection, ) @@ -529,7 +513,6 @@ from .directory_type import DirectoryType as DirectoryType from .directory_user import DirectoryUser as DirectoryUser from .directory_user_email import DirectoryUserEmail as DirectoryUserEmail -from .directory_user_state import * from .directory_user_with_groups_state import ( DirectoryUserWithGroupsState as DirectoryUserWithGroupsState, ) @@ -538,12 +521,8 @@ from .dsync_activated_data_domain import ( DsyncActivatedDataDomain as DsyncActivatedDataDomain, ) -from .dsync_activated_data_state import * -from .dsync_activated_data_type import * from .dsync_deleted import DsyncDeleted as DsyncDeleted from .dsync_deleted_data import DsyncDeletedData as DsyncDeletedData -from .dsync_deleted_data_state import * -from .dsync_deleted_data_type import * from .dsync_group_created import DsyncGroupCreated as DsyncGroupCreated from .dsync_group_deleted import DsyncGroupDeleted as DsyncGroupDeleted from .dsync_group_updated import DsyncGroupUpdated as DsyncGroupUpdated @@ -567,7 +546,6 @@ from .dsync_user_updated_data_email import ( DsyncUserUpdatedDataEmail as DsyncUserUpdatedDataEmail, ) -from .dsync_user_updated_data_state import * from .email_verification_created import ( EmailVerificationCreated as EmailVerificationCreated, ) @@ -577,7 +555,6 @@ from .error_response import ErrorResponse as ErrorResponse from .event_context import EventContext as EventContext from .event_context_actor import EventContextActor as EventContextActor -from .event_context_actor_source import * from .event_context_google_analytics_session import ( EventContextGoogleAnalyticsSession as EventContextGoogleAnalyticsSession, ) @@ -589,7 +566,6 @@ from .flag_created_context_actor import ( FlagCreatedContextActor as FlagCreatedContextActor, ) -from .flag_created_context_actor_source import * from .flag_created_data import FlagCreatedData as FlagCreatedData from .flag_created_data_owner import FlagCreatedDataOwner as FlagCreatedDataOwner from .flag_deleted import FlagDeleted as FlagDeleted @@ -597,17 +573,14 @@ from .flag_deleted_context_actor import ( FlagDeletedContextActor as FlagDeletedContextActor, ) -from .flag_deleted_context_actor_source import * from .flag_deleted_data import FlagDeletedData as FlagDeletedData from .flag_deleted_data_owner import FlagDeletedDataOwner as FlagDeletedDataOwner from .flag_owner import FlagOwner as FlagOwner from .flag_rule_updated import FlagRuleUpdated as FlagRuleUpdated from .flag_rule_updated_context import FlagRuleUpdatedContext as FlagRuleUpdatedContext -from .flag_rule_updated_context_access_type import * from .flag_rule_updated_context_actor import ( FlagRuleUpdatedContextActor as FlagRuleUpdatedContextActor, ) -from .flag_rule_updated_context_actor_source import * from .flag_rule_updated_context_configured_target import ( FlagRuleUpdatedContextConfiguredTarget as FlagRuleUpdatedContextConfiguredTarget, ) @@ -623,7 +596,6 @@ from .flag_rule_updated_context_previous_attribute_context import ( FlagRuleUpdatedContextPreviousAttributeContext as FlagRuleUpdatedContextPreviousAttributeContext, ) -from .flag_rule_updated_context_previous_attribute_context_access_type import * from .flag_rule_updated_context_previous_attribute_context_configured_target import ( FlagRuleUpdatedContextPreviousAttributeContextConfiguredTarget as FlagRuleUpdatedContextPreviousAttributeContextConfiguredTarget, ) @@ -645,7 +617,6 @@ from .flag_updated_context_actor import ( FlagUpdatedContextActor as FlagUpdatedContextActor, ) -from .flag_updated_context_actor_source import * from .flag_updated_context_previous_attribute import ( FlagUpdatedContextPreviousAttribute as FlagUpdatedContextPreviousAttribute, ) @@ -665,16 +636,12 @@ from .group_updated import GroupUpdated as GroupUpdated from .invitation_accepted import InvitationAccepted as InvitationAccepted from .invitation_accepted_data import InvitationAcceptedData as InvitationAcceptedData -from .invitation_accepted_data_state import * from .invitation_created import InvitationCreated as InvitationCreated from .invitation_created_data import InvitationCreatedData as InvitationCreatedData -from .invitation_created_data_state import * from .invitation_resent import InvitationResent as InvitationResent from .invitation_resent_data import InvitationResentData as InvitationResentData -from .invitation_resent_data_state import * from .invitation_revoked import InvitationRevoked as InvitationRevoked from .invitation_revoked_data import InvitationRevokedData as InvitationRevokedData -from .invitation_revoked_data_state import * from .invitation_state import InvitationState as InvitationState from .invite_it_contact_intents import InviteItContactIntents as InviteItContactIntents from .list_metadata import ListMetadata as ListMetadata @@ -687,8 +654,6 @@ from .organization_created_data_domain import ( OrganizationCreatedDataDomain as OrganizationCreatedDataDomain, ) -from .organization_created_data_domain_state import * -from .organization_created_data_domain_verification_strategy import * from .organization_deleted import OrganizationDeleted as OrganizationDeleted from .organization_deleted_data import ( OrganizationDeletedData as OrganizationDeletedData, @@ -696,8 +661,6 @@ from .organization_deleted_data_domain import ( OrganizationDeletedDataDomain as OrganizationDeletedDataDomain, ) -from .organization_deleted_data_domain_state import * -from .organization_deleted_data_domain_verification_strategy import * from .organization_domain import OrganizationDomain as OrganizationDomain from .organization_domain_created import ( OrganizationDomainCreated as OrganizationDomainCreated, @@ -705,8 +668,6 @@ from .organization_domain_created_data import ( OrganizationDomainCreatedData as OrganizationDomainCreatedData, ) -from .organization_domain_created_data_state import * -from .organization_domain_created_data_verification_strategy import * from .organization_domain_data_state import ( OrganizationDomainDataState as OrganizationDomainDataState, ) @@ -716,8 +677,6 @@ from .organization_domain_deleted_data import ( OrganizationDomainDeletedData as OrganizationDomainDeletedData, ) -from .organization_domain_deleted_data_state import * -from .organization_domain_deleted_data_verification_strategy import * from .organization_domain_state import ( OrganizationDomainState as OrganizationDomainState, ) @@ -727,8 +686,6 @@ from .organization_domain_updated_data import ( OrganizationDomainUpdatedData as OrganizationDomainUpdatedData, ) -from .organization_domain_updated_data_state import * -from .organization_domain_updated_data_verification_strategy import * from .organization_domain_verification_failed import ( OrganizationDomainVerificationFailed as OrganizationDomainVerificationFailed, ) @@ -738,9 +695,6 @@ from .organization_domain_verification_failed_data_organization_domain import ( OrganizationDomainVerificationFailedDataOrganizationDomain as OrganizationDomainVerificationFailedDataOrganizationDomain, ) -from .organization_domain_verification_failed_data_organization_domain_state import * -from .organization_domain_verification_failed_data_organization_domain_verification_strategy import * -from .organization_domain_verification_failed_data_reason import * from .organization_domain_verification_strategy import ( OrganizationDomainVerificationStrategy as OrganizationDomainVerificationStrategy, ) @@ -750,22 +704,18 @@ from .organization_domain_verified_data import ( OrganizationDomainVerifiedData as OrganizationDomainVerifiedData, ) -from .organization_domain_verified_data_state import * -from .organization_domain_verified_data_verification_strategy import * from .organization_membership_created import ( OrganizationMembershipCreated as OrganizationMembershipCreated, ) from .organization_membership_created_data import ( OrganizationMembershipCreatedData as OrganizationMembershipCreatedData, ) -from .organization_membership_created_data_status import * from .organization_membership_deleted import ( OrganizationMembershipDeleted as OrganizationMembershipDeleted, ) from .organization_membership_deleted_data import ( OrganizationMembershipDeletedData as OrganizationMembershipDeletedData, ) -from .organization_membership_deleted_data_status import * from .organization_membership_status import ( OrganizationMembershipStatus as OrganizationMembershipStatus, ) @@ -775,7 +725,6 @@ from .organization_membership_updated_data import ( OrganizationMembershipUpdatedData as OrganizationMembershipUpdatedData, ) -from .organization_membership_updated_data_status import * from .organization_role_created import ( OrganizationRoleCreated as OrganizationRoleCreated, ) @@ -801,8 +750,6 @@ from .organization_updated_data_domain import ( OrganizationUpdatedDataDomain as OrganizationUpdatedDataDomain, ) -from .organization_updated_data_domain_state import * -from .organization_updated_data_domain_verification_strategy import * from .pagination_order import PaginationOrder as PaginationOrder from .password_reset_created import PasswordResetCreated as PasswordResetCreated from .password_reset_created_data import ( @@ -825,7 +772,6 @@ from .permission_updated import PermissionUpdated as PermissionUpdated from .permission_updated_data import PermissionUpdatedData as PermissionUpdatedData from .pipe_connected_account import PipeConnectedAccount as PipeConnectedAccount -from .pipe_connected_account_state import * from .pipes_account_connection_add_failed import ( PipesAccountConnectionAddFailed as PipesAccountConnectionAddFailed, ) @@ -891,24 +837,20 @@ from .resource_export_completed_data import ( ResourceExportCompletedData as ResourceExportCompletedData, ) -from .resource_export_completed_data_resource_type import * from .resource_export_created import ResourceExportCreated as ResourceExportCreated from .resource_export_created_data import ( ResourceExportCreatedData as ResourceExportCreatedData, ) -from .resource_export_created_data_resource_type import * from .resource_export_downloaded import ( ResourceExportDownloaded as ResourceExportDownloaded, ) from .resource_export_downloaded_data import ( ResourceExportDownloadedData as ResourceExportDownloadedData, ) -from .resource_export_downloaded_data_resource_type import * from .resource_export_failed import ResourceExportFailed as ResourceExportFailed from .resource_export_failed_data import ( ResourceExportFailedData as ResourceExportFailedData, ) -from .resource_export_failed_data_resource_type import * from .role_created import RoleCreated as RoleCreated from .role_created_data import RoleCreatedData as RoleCreatedData from .role_deleted import RoleDeleted as RoleDeleted @@ -918,18 +860,14 @@ from .role_updated_data import RoleUpdatedData as RoleUpdatedData from .session_created import SessionCreated as SessionCreated from .session_created_data import SessionCreatedData as SessionCreatedData -from .session_created_data_auth_method import * from .session_created_data_impersonator import ( SessionCreatedDataImpersonator as SessionCreatedDataImpersonator, ) -from .session_created_data_status import * from .session_revoked import SessionRevoked as SessionRevoked from .session_revoked_data import SessionRevokedData as SessionRevokedData -from .session_revoked_data_auth_method import * from .session_revoked_data_impersonator import ( SessionRevokedDataImpersonator as SessionRevokedDataImpersonator, ) -from .session_revoked_data_status import * from .slim_role import SlimRole as SlimRole from .team_production_state import TeamProductionState as TeamProductionState from .token_query_grant_type import TokenQueryGrantType as TokenQueryGrantType @@ -990,7 +928,6 @@ from .vault_byok_key_deleted_data import ( VaultByokKeyDeletedData as VaultByokKeyDeletedData, ) -from .vault_byok_key_provider import * from .vault_byok_key_verification_completed import ( VaultByokKeyVerificationCompleted as VaultByokKeyVerificationCompleted, ) @@ -999,40 +936,29 @@ ) from .vault_data_created import VaultDataCreated as VaultDataCreated from .vault_data_created_data import VaultDataCreatedData as VaultDataCreatedData -from .vault_data_created_data_actor_source import * from .vault_data_deleted import VaultDataDeleted as VaultDataDeleted from .vault_data_deleted_data import VaultDataDeletedData as VaultDataDeletedData -from .vault_data_deleted_data_actor_source import * from .vault_data_read import VaultDataRead as VaultDataRead from .vault_data_read_data import VaultDataReadData as VaultDataReadData -from .vault_data_read_data_actor_source import * from .vault_data_updated import VaultDataUpdated as VaultDataUpdated from .vault_data_updated_data import VaultDataUpdatedData as VaultDataUpdatedData -from .vault_data_updated_data_actor_source import * from .vault_dek_decrypted import VaultDekDecrypted as VaultDekDecrypted from .vault_dek_decrypted_data import VaultDekDecryptedData as VaultDekDecryptedData -from .vault_dek_decrypted_data_actor_source import * from .vault_dek_read import VaultDekRead as VaultDekRead from .vault_dek_read_data import VaultDekReadData as VaultDekReadData -from .vault_dek_read_data_actor_source import * from .vault_kek_created import VaultKekCreated as VaultKekCreated from .vault_kek_created_data import VaultKekCreatedData as VaultKekCreatedData -from .vault_kek_created_data_actor_source import * from .vault_kek_deleted import VaultKekDeleted as VaultKekDeleted from .vault_kek_deleted_data import VaultKekDeletedData as VaultKekDeletedData -from .vault_kek_deleted_data_actor_source import * from .vault_metadata_read import VaultMetadataRead as VaultMetadataRead from .vault_metadata_read_data import VaultMetadataReadData as VaultMetadataReadData -from .vault_metadata_read_data_actor_source import * from .vault_names_listed import VaultNamesListed as VaultNamesListed from .vault_names_listed_data import VaultNamesListedData as VaultNamesListedData -from .vault_names_listed_data_actor_source import * from .waitlist_entry_state import WaitlistEntryState as WaitlistEntryState from .waitlist_user import WaitlistUser as WaitlistUser from .waitlist_user_approved import WaitlistUserApproved as WaitlistUserApproved from .waitlist_user_created import WaitlistUserCreated as WaitlistUserCreated from .waitlist_user_denied import WaitlistUserDenied as WaitlistUserDenied -from .waitlist_user_state import * from .webhook_endpoint_status import WebhookEndpointStatus as WebhookEndpointStatus from .widget_session_token_scopes import ( WidgetSessionTokenScopes as WidgetSessionTokenScopes, diff --git a/src/workos/organizations_data_providers/__init__.py b/src/workos/organizations_data_providers/__init__.py index b773fa2da..33e9c7b78 100644 --- a/src/workos/organizations_data_providers/__init__.py +++ b/src/workos/organizations_data_providers/__init__.py @@ -1,7 +1,2 @@ # This file is auto-generated by oagen. Do not edit. -from ._resource import ( - AsyncOrganizationsDataProviders as AsyncOrganizationsDataProviders, -) -from ._resource import OrganizationsDataProviders as OrganizationsDataProviders -from .models import * diff --git a/src/workos/organizations_data_providers/_resource.py b/src/workos/organizations_data_providers/_resource.py deleted file mode 100644 index f593134db..000000000 --- a/src/workos/organizations_data_providers/_resource.py +++ /dev/null @@ -1,584 +0,0 @@ -# This file is auto-generated by oagen. Do not edit. - -from __future__ import annotations - -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from .._client import AsyncWorkOSClient, WorkOSClient - -from workos.common.models.connected_account import ConnectedAccount -from workos.common.models.connected_account_input_state import ( - ConnectedAccountInputState, -) -from workos.common.models.data_integrations_list_response import ( - DataIntegrationsListResponse, -) - -from .._types import RequestOptions, enum_value - - -class OrganizationsDataProviders: - """Organizations Data Providers API resources.""" - - def __init__(self, client: WorkOSClient) -> None: - self._client = client - - def get_organization_connected_account( - self, - organization_id: str, - slug: str, - *, - supports_multiple_connections: bool | None = None, - connected_account_id: str | None = None, - request_options: RequestOptions | None = None, - ) -> ConnectedAccount: - """Get an organization connected account - - Retrieves an organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) for a specific provider. - - Args: - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. - slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). - supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. - connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the organization has several for this provider. - request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - - Returns: - ConnectedAccount - - Raises: - BadRequestError: If the request is malformed (400). - AuthenticationError: If the API key is invalid (401). - NotFoundError: If the resource is not found (404). - ConflictError: If a conflict occurs (409). - RateLimitExceededError: If rate limited (429). - ServerError: If the server returns a 5xx error. - """ - params: dict[str, Any] = { - k: v - for k, v in { - "supports_multiple_connections": supports_multiple_connections, - "connected_account_id": connected_account_id, - }.items() - if v is not None - } - return self._client.request( - method="get", - path=( - "organizations", - str(organization_id), - "connected_accounts", - str(slug), - ), - params=params, - model=ConnectedAccount, - request_options=request_options, - ) - - def create_organization_connected_account( - self, - organization_id: str, - slug: str, - *, - access_token: str | None = None, - refresh_token: str | None = None, - expires_at: str | None = None, - scopes: list[str] | None = None, - state: ConnectedAccountInputState | str | None = None, - request_options: RequestOptions | None = None, - ) -> ConnectedAccount: - """Import an organization connected account - - Imports an organization-owned [connected account](https://workos.com/docs/reference/pipes/connected-account) by providing OAuth tokens directly. Use this to migrate existing connections or set up connections without going through the OAuth flow. - - Args: - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. - slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). - access_token: The OAuth access token for the connected account. - refresh_token: The OAuth refresh token for the connected account. - expires_at: The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. - scopes: The OAuth scopes granted for this connection. - state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. - request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - - Returns: - ConnectedAccount - - Raises: - AuthenticationError: If the API key is invalid (401). - NotFoundError: If the resource is not found (404). - ConflictError: If a conflict occurs (409). - UnprocessableEntityError: If the request data is unprocessable (422). - RateLimitExceededError: If rate limited (429). - ServerError: If the server returns a 5xx error. - """ - body: dict[str, Any] = { - k: v - for k, v in { - "access_token": access_token, - "refresh_token": refresh_token, - "expires_at": expires_at, - "scopes": scopes, - "state": enum_value(state) if state is not None else None, - }.items() - if v is not None - } - return self._client.request( - method="post", - path=( - "organizations", - str(organization_id), - "connected_accounts", - str(slug), - ), - body=body, - model=ConnectedAccount, - request_options=request_options, - ) - - def update_organization_connected_account( - self, - organization_id: str, - slug: str, - *, - access_token: str | None = None, - refresh_token: str | None = None, - expires_at: str | None = None, - scopes: list[str] | None = None, - state: ConnectedAccountInputState | str | None = None, - supports_multiple_connections: bool | None = None, - connected_account_id: str | None = None, - request_options: RequestOptions | None = None, - ) -> ConnectedAccount: - """Update an organization connected account - - Updates an organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) tokens, scopes, or state for a specific provider. - - Args: - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. - slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). - access_token: The OAuth access token for the connected account. - refresh_token: The OAuth refresh token for the connected account. - expires_at: The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. - scopes: The OAuth scopes granted for this connection. - state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. - supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. - connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to update. - request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - - Returns: - ConnectedAccount - - Raises: - BadRequestError: If the request is malformed (400). - AuthenticationError: If the API key is invalid (401). - NotFoundError: If the resource is not found (404). - ConflictError: If a conflict occurs (409). - UnprocessableEntityError: If the request data is unprocessable (422). - RateLimitExceededError: If rate limited (429). - ServerError: If the server returns a 5xx error. - """ - body: dict[str, Any] = { - k: v - for k, v in { - "access_token": access_token, - "refresh_token": refresh_token, - "expires_at": expires_at, - "scopes": scopes, - "state": enum_value(state) if state is not None else None, - }.items() - if v is not None - } - params: dict[str, Any] = { - k: v - for k, v in { - "supports_multiple_connections": supports_multiple_connections, - "connected_account_id": connected_account_id, - }.items() - if v is not None - } - return self._client.request( - method="put", - path=( - "organizations", - str(organization_id), - "connected_accounts", - str(slug), - ), - body=body, - params=params, - model=ConnectedAccount, - request_options=request_options, - ) - - def delete_organization_connected_account( - self, - organization_id: str, - slug: str, - *, - supports_multiple_connections: bool | None = None, - connected_account_id: str | None = None, - request_options: RequestOptions | None = None, - ) -> None: - """Delete an organization connected account - - Disconnects the organization's account for the provider, including removing any stored access and refresh tokens. A member will need to reauthorize if the organization wants to reconnect. This does not revoke access on the provider side. - - Args: - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. - slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). - supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. - connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to delete. - request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - - Raises: - BadRequestError: If the request is malformed (400). - AuthenticationError: If the API key is invalid (401). - NotFoundError: If the resource is not found (404). - ConflictError: If a conflict occurs (409). - RateLimitExceededError: If rate limited (429). - ServerError: If the server returns a 5xx error. - """ - params: dict[str, Any] = { - k: v - for k, v in { - "supports_multiple_connections": supports_multiple_connections, - "connected_account_id": connected_account_id, - }.items() - if v is not None - } - self._client.request( - method="delete", - path=( - "organizations", - str(organization_id), - "connected_accounts", - str(slug), - ), - params=params, - request_options=request_options, - ) - - def list_organization_data_providers( - self, - organization_id: str, - *, - supports_multiple_connections: bool | None = None, - request_options: RequestOptions | None = None, - ) -> DataIntegrationsListResponse: - """List providers for an organization - - Retrieves the organization-owned providers configured for your environment and the organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) information for each. Providers owned by individual users are not included. - - Args: - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier to list providers and connected accounts for. - supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. - request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - - Returns: - DataIntegrationsListResponse - - Raises: - AuthenticationError: If the API key is invalid (401). - NotFoundError: If the resource is not found (404). - RateLimitExceededError: If rate limited (429). - ServerError: If the server returns a 5xx error. - """ - params: dict[str, Any] = { - k: v - for k, v in { - "supports_multiple_connections": supports_multiple_connections, - }.items() - if v is not None - } - return self._client.request( - method="get", - path=("organizations", str(organization_id), "data_providers"), - params=params, - model=DataIntegrationsListResponse, - request_options=request_options, - ) - - -class AsyncOrganizationsDataProviders: - """Organizations Data Providers API resources (async).""" - - def __init__(self, client: AsyncWorkOSClient) -> None: - self._client = client - - async def get_organization_connected_account( - self, - organization_id: str, - slug: str, - *, - supports_multiple_connections: bool | None = None, - connected_account_id: str | None = None, - request_options: RequestOptions | None = None, - ) -> ConnectedAccount: - """Get an organization connected account - - Retrieves an organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) for a specific provider. - - Args: - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. - slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). - supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. - connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the organization has several for this provider. - request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - - Returns: - ConnectedAccount - - Raises: - BadRequestError: If the request is malformed (400). - AuthenticationError: If the API key is invalid (401). - NotFoundError: If the resource is not found (404). - ConflictError: If a conflict occurs (409). - RateLimitExceededError: If rate limited (429). - ServerError: If the server returns a 5xx error. - """ - params: dict[str, Any] = { - k: v - for k, v in { - "supports_multiple_connections": supports_multiple_connections, - "connected_account_id": connected_account_id, - }.items() - if v is not None - } - return await self._client.request( - method="get", - path=( - "organizations", - str(organization_id), - "connected_accounts", - str(slug), - ), - params=params, - model=ConnectedAccount, - request_options=request_options, - ) - - async def create_organization_connected_account( - self, - organization_id: str, - slug: str, - *, - access_token: str | None = None, - refresh_token: str | None = None, - expires_at: str | None = None, - scopes: list[str] | None = None, - state: ConnectedAccountInputState | str | None = None, - request_options: RequestOptions | None = None, - ) -> ConnectedAccount: - """Import an organization connected account - - Imports an organization-owned [connected account](https://workos.com/docs/reference/pipes/connected-account) by providing OAuth tokens directly. Use this to migrate existing connections or set up connections without going through the OAuth flow. - - Args: - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. - slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). - access_token: The OAuth access token for the connected account. - refresh_token: The OAuth refresh token for the connected account. - expires_at: The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. - scopes: The OAuth scopes granted for this connection. - state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. - request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - - Returns: - ConnectedAccount - - Raises: - AuthenticationError: If the API key is invalid (401). - NotFoundError: If the resource is not found (404). - ConflictError: If a conflict occurs (409). - UnprocessableEntityError: If the request data is unprocessable (422). - RateLimitExceededError: If rate limited (429). - ServerError: If the server returns a 5xx error. - """ - body: dict[str, Any] = { - k: v - for k, v in { - "access_token": access_token, - "refresh_token": refresh_token, - "expires_at": expires_at, - "scopes": scopes, - "state": enum_value(state) if state is not None else None, - }.items() - if v is not None - } - return await self._client.request( - method="post", - path=( - "organizations", - str(organization_id), - "connected_accounts", - str(slug), - ), - body=body, - model=ConnectedAccount, - request_options=request_options, - ) - - async def update_organization_connected_account( - self, - organization_id: str, - slug: str, - *, - access_token: str | None = None, - refresh_token: str | None = None, - expires_at: str | None = None, - scopes: list[str] | None = None, - state: ConnectedAccountInputState | str | None = None, - supports_multiple_connections: bool | None = None, - connected_account_id: str | None = None, - request_options: RequestOptions | None = None, - ) -> ConnectedAccount: - """Update an organization connected account - - Updates an organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) tokens, scopes, or state for a specific provider. - - Args: - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. - slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). - access_token: The OAuth access token for the connected account. - refresh_token: The OAuth refresh token for the connected account. - expires_at: The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. - scopes: The OAuth scopes granted for this connection. - state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. - supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. - connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to update. - request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - - Returns: - ConnectedAccount - - Raises: - BadRequestError: If the request is malformed (400). - AuthenticationError: If the API key is invalid (401). - NotFoundError: If the resource is not found (404). - ConflictError: If a conflict occurs (409). - UnprocessableEntityError: If the request data is unprocessable (422). - RateLimitExceededError: If rate limited (429). - ServerError: If the server returns a 5xx error. - """ - body: dict[str, Any] = { - k: v - for k, v in { - "access_token": access_token, - "refresh_token": refresh_token, - "expires_at": expires_at, - "scopes": scopes, - "state": enum_value(state) if state is not None else None, - }.items() - if v is not None - } - params: dict[str, Any] = { - k: v - for k, v in { - "supports_multiple_connections": supports_multiple_connections, - "connected_account_id": connected_account_id, - }.items() - if v is not None - } - return await self._client.request( - method="put", - path=( - "organizations", - str(organization_id), - "connected_accounts", - str(slug), - ), - body=body, - params=params, - model=ConnectedAccount, - request_options=request_options, - ) - - async def delete_organization_connected_account( - self, - organization_id: str, - slug: str, - *, - supports_multiple_connections: bool | None = None, - connected_account_id: str | None = None, - request_options: RequestOptions | None = None, - ) -> None: - """Delete an organization connected account - - Disconnects the organization's account for the provider, including removing any stored access and refresh tokens. A member will need to reauthorize if the organization wants to reconnect. This does not revoke access on the provider side. - - Args: - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. - slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). - supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. - connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to delete. - request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - - Raises: - BadRequestError: If the request is malformed (400). - AuthenticationError: If the API key is invalid (401). - NotFoundError: If the resource is not found (404). - ConflictError: If a conflict occurs (409). - RateLimitExceededError: If rate limited (429). - ServerError: If the server returns a 5xx error. - """ - params: dict[str, Any] = { - k: v - for k, v in { - "supports_multiple_connections": supports_multiple_connections, - "connected_account_id": connected_account_id, - }.items() - if v is not None - } - await self._client.request( - method="delete", - path=( - "organizations", - str(organization_id), - "connected_accounts", - str(slug), - ), - params=params, - request_options=request_options, - ) - - async def list_organization_data_providers( - self, - organization_id: str, - *, - supports_multiple_connections: bool | None = None, - request_options: RequestOptions | None = None, - ) -> DataIntegrationsListResponse: - """List providers for an organization - - Retrieves the organization-owned providers configured for your environment and the organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) information for each. Providers owned by individual users are not included. - - Args: - organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier to list providers and connected accounts for. - supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. - request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. - - Returns: - DataIntegrationsListResponse - - Raises: - AuthenticationError: If the API key is invalid (401). - NotFoundError: If the resource is not found (404). - RateLimitExceededError: If rate limited (429). - ServerError: If the server returns a 5xx error. - """ - params: dict[str, Any] = { - k: v - for k, v in { - "supports_multiple_connections": supports_multiple_connections, - }.items() - if v is not None - } - return await self._client.request( - method="get", - path=("organizations", str(organization_id), "data_providers"), - params=params, - model=DataIntegrationsListResponse, - request_options=request_options, - ) diff --git a/src/workos/organizations_data_providers/models/__init__.py b/src/workos/organizations_data_providers/models/__init__.py index 5271119e8..33e9c7b78 100644 --- a/src/workos/organizations_data_providers/models/__init__.py +++ b/src/workos/organizations_data_providers/models/__init__.py @@ -1,14 +1,2 @@ # This file is auto-generated by oagen. Do not edit. -from workos.common.models.connected_account_input import ( - ConnectedAccountInput as ConnectedAccountInput, -) -from workos.common.models.data_integrations_list_response import ( - DataIntegrationsListResponse as DataIntegrationsListResponse, -) -from workos.common.models.data_integrations_list_response_data import ( - DataIntegrationsListResponseData as DataIntegrationsListResponseData, -) -from workos.common.models.data_integrations_list_response_data_connected_account import ( - DataIntegrationsListResponseDataConnectedAccount as DataIntegrationsListResponseDataConnectedAccount, -) diff --git a/src/workos/pipes/_resource.py b/src/workos/pipes/_resource.py index d716edff8..91a888757 100644 --- a/src/workos/pipes/_resource.py +++ b/src/workos/pipes/_resource.py @@ -731,6 +731,282 @@ def get_access_token( request_options=request_options, ) + def get_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + supports_multiple_connections: bool | None = None, + connected_account_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> ConnectedAccount: + """Get an organization connected account + + Retrieves an organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) for a specific provider. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the organization has several for this provider. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ConnectedAccount + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + "connected_account_id": connected_account_id, + }.items() + if v is not None + } + return self._client.request( + method="get", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + params=params, + model=ConnectedAccount, + request_options=request_options, + ) + + def create_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + access_token: str | None = None, + refresh_token: str | None = None, + expires_at: str | None = None, + scopes: list[str] | None = None, + state: ConnectedAccountInputState | str | None = None, + request_options: RequestOptions | None = None, + ) -> ConnectedAccount: + """Import an organization connected account + + Imports an organization-owned [connected account](https://workos.com/docs/reference/pipes/connected-account) by providing OAuth tokens directly. Use this to migrate existing connections or set up connections without going through the OAuth flow. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + access_token: The OAuth access token for the connected account. + refresh_token: The OAuth refresh token for the connected account. + expires_at: The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. + scopes: The OAuth scopes granted for this connection. + state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ConnectedAccount + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "access_token": access_token, + "refresh_token": refresh_token, + "expires_at": expires_at, + "scopes": scopes, + "state": enum_value(state) if state is not None else None, + }.items() + if v is not None + } + return self._client.request( + method="post", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + body=body, + model=ConnectedAccount, + request_options=request_options, + ) + + def update_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + access_token: str | None = None, + refresh_token: str | None = None, + expires_at: str | None = None, + scopes: list[str] | None = None, + state: ConnectedAccountInputState | str | None = None, + supports_multiple_connections: bool | None = None, + connected_account_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> ConnectedAccount: + """Update an organization connected account + + Updates an organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) tokens, scopes, or state for a specific provider. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + access_token: The OAuth access token for the connected account. + refresh_token: The OAuth refresh token for the connected account. + expires_at: The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. + scopes: The OAuth scopes granted for this connection. + state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to update. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ConnectedAccount + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "access_token": access_token, + "refresh_token": refresh_token, + "expires_at": expires_at, + "scopes": scopes, + "state": enum_value(state) if state is not None else None, + }.items() + if v is not None + } + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + "connected_account_id": connected_account_id, + }.items() + if v is not None + } + return self._client.request( + method="put", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + body=body, + params=params, + model=ConnectedAccount, + request_options=request_options, + ) + + def delete_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + supports_multiple_connections: bool | None = None, + connected_account_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an organization connected account + + Disconnects the organization's account for the provider, including removing any stored access and refresh tokens. A member will need to reauthorize if the organization wants to reconnect. This does not revoke access on the provider side. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to delete. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + "connected_account_id": connected_account_id, + }.items() + if v is not None + } + self._client.request( + method="delete", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + params=params, + request_options=request_options, + ) + + def list_organization_data_providers( + self, + organization_id: str, + *, + supports_multiple_connections: bool | None = None, + request_options: RequestOptions | None = None, + ) -> DataIntegrationsListResponse: + """List providers for an organization + + Retrieves the organization-owned providers configured for your environment and the organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) information for each. Providers owned by individual users are not included. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier to list providers and connected accounts for. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + DataIntegrationsListResponse + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + }.items() + if v is not None + } + return self._client.request( + method="get", + path=("organizations", str(organization_id), "data_providers"), + params=params, + model=DataIntegrationsListResponse, + request_options=request_options, + ) + def get_user_connected_account( self, user_id: str, @@ -1713,6 +1989,282 @@ async def get_access_token( request_options=request_options, ) + async def get_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + supports_multiple_connections: bool | None = None, + connected_account_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> ConnectedAccount: + """Get an organization connected account + + Retrieves an organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) for a specific provider. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select a specific connection when the organization has several for this provider. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ConnectedAccount + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + "connected_account_id": connected_account_id, + }.items() + if v is not None + } + return await self._client.request( + method="get", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + params=params, + model=ConnectedAccount, + request_options=request_options, + ) + + async def create_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + access_token: str | None = None, + refresh_token: str | None = None, + expires_at: str | None = None, + scopes: list[str] | None = None, + state: ConnectedAccountInputState | str | None = None, + request_options: RequestOptions | None = None, + ) -> ConnectedAccount: + """Import an organization connected account + + Imports an organization-owned [connected account](https://workos.com/docs/reference/pipes/connected-account) by providing OAuth tokens directly. Use this to migrate existing connections or set up connections without going through the OAuth flow. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + access_token: The OAuth access token for the connected account. + refresh_token: The OAuth refresh token for the connected account. + expires_at: The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. + scopes: The OAuth scopes granted for this connection. + state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ConnectedAccount + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "access_token": access_token, + "refresh_token": refresh_token, + "expires_at": expires_at, + "scopes": scopes, + "state": enum_value(state) if state is not None else None, + }.items() + if v is not None + } + return await self._client.request( + method="post", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + body=body, + model=ConnectedAccount, + request_options=request_options, + ) + + async def update_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + access_token: str | None = None, + refresh_token: str | None = None, + expires_at: str | None = None, + scopes: list[str] | None = None, + state: ConnectedAccountInputState | str | None = None, + supports_multiple_connections: bool | None = None, + connected_account_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> ConnectedAccount: + """Update an organization connected account + + Updates an organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) tokens, scopes, or state for a specific provider. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + access_token: The OAuth access token for the connected account. + refresh_token: The OAuth refresh token for the connected account. + expires_at: The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire. + scopes: The OAuth scopes granted for this connection. + state: Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to update. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + ConnectedAccount + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + UnprocessableEntityError: If the request data is unprocessable (422). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: dict[str, Any] = { + k: v + for k, v in { + "access_token": access_token, + "refresh_token": refresh_token, + "expires_at": expires_at, + "scopes": scopes, + "state": enum_value(state) if state is not None else None, + }.items() + if v is not None + } + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + "connected_account_id": connected_account_id, + }.items() + if v is not None + } + return await self._client.request( + method="put", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + body=body, + params=params, + model=ConnectedAccount, + request_options=request_options, + ) + + async def delete_organization_connected_account( + self, + organization_id: str, + slug: str, + *, + supports_multiple_connections: bool | None = None, + connected_account_id: str | None = None, + request_options: RequestOptions | None = None, + ) -> None: + """Delete an organization connected account + + Disconnects the organization's account for the provider, including removing any stored access and refresh tokens. A member will need to reauthorize if the organization wants to reconnect. This does not revoke access on the provider side. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. + slug: The slug identifier of the provider (e.g., `github`, `slack`, `notion`). + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + connected_account_id: A [connected account](https://workos.com/docs/reference/pipes/connected-account) identifier. Use this to select the connection to delete. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + ConflictError: If a conflict occurs (409). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + "connected_account_id": connected_account_id, + }.items() + if v is not None + } + await self._client.request( + method="delete", + path=( + "organizations", + str(organization_id), + "connected_accounts", + str(slug), + ), + params=params, + request_options=request_options, + ) + + async def list_organization_data_providers( + self, + organization_id: str, + *, + supports_multiple_connections: bool | None = None, + request_options: RequestOptions | None = None, + ) -> DataIntegrationsListResponse: + """List providers for an organization + + Retrieves the organization-owned providers configured for your environment and the organization's [connected account](https://workos.com/docs/reference/pipes/connected-account) information for each. Providers owned by individual users are not included. + + Args: + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier to list providers and connected accounts for. + supports_multiple_connections: Set to `true` to use the plural connection contract. When omitted or `false`, only the compatibility connection is considered. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + DataIntegrationsListResponse + + Raises: + AuthenticationError: If the API key is invalid (401). + NotFoundError: If the resource is not found (404). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params: dict[str, Any] = { + k: v + for k, v in { + "supports_multiple_connections": supports_multiple_connections, + }.items() + if v is not None + } + return await self._client.request( + method="get", + path=("organizations", str(organization_id), "data_providers"), + params=params, + model=DataIntegrationsListResponse, + request_options=request_options, + ) + async def get_user_connected_account( self, user_id: str, diff --git a/src/workos/pipes/models/__init__.py b/src/workos/pipes/models/__init__.py index dd91230f6..f27a0080c 100644 --- a/src/workos/pipes/models/__init__.py +++ b/src/workos/pipes/models/__init__.py @@ -1,9 +1,20 @@ # This file is auto-generated by oagen. Do not edit. from workos.common.models.connected_account import ConnectedAccount as ConnectedAccount +from workos.common.models.connected_account_input import ( + ConnectedAccountInput as ConnectedAccountInput, +) +from workos.common.models.data_integrations_list_response import ( + DataIntegrationsListResponse as DataIntegrationsListResponse, +) +from workos.common.models.data_integrations_list_response_data import ( + DataIntegrationsListResponseData as DataIntegrationsListResponseData, +) +from workos.common.models.data_integrations_list_response_data_connected_account import ( + DataIntegrationsListResponseDataConnectedAccount as DataIntegrationsListResponseDataConnectedAccount, +) from .api_key_installation import ApiKeyInstallation as ApiKeyInstallation -from .connected_account_input import * from .create_data_integration import CreateDataIntegration as CreateDataIntegration from .custom_provider_definition import ( CustomProviderDefinition as CustomProviderDefinition, @@ -42,9 +53,6 @@ from .data_integrations_get_user_token_request import ( DataIntegrationsGetUserTokenRequest as DataIntegrationsGetUserTokenRequest, ) -from .data_integrations_list_response import * -from .data_integrations_list_response_data import * -from .data_integrations_list_response_data_connected_account import * from .data_integrations_upsert_api_key_request import ( DataIntegrationsUpsertApiKeyRequest as DataIntegrationsUpsertApiKeyRequest, ) diff --git a/src/workos/pipes/models/connected_account_input.py b/src/workos/pipes/models/connected_account_input.py deleted file mode 100644 index 2bd2eb1db..000000000 --- a/src/workos/pipes/models/connected_account_input.py +++ /dev/null @@ -1,64 +0,0 @@ -# This file is auto-generated by oagen. Do not edit. - -from __future__ import annotations - -from dataclasses import dataclass -from datetime import datetime -from enum import Enum -from typing import Any - -from workos._types import _format_datetime, _parse_datetime, _raise_deserialize_error -from workos.common.models.connected_account_input_state import ( - ConnectedAccountInputState, -) - - -@dataclass(slots=True) -class ConnectedAccountInput: - """Connected Account Input model.""" - - access_token: str | None = None - """The OAuth access token for the connected account.""" - refresh_token: str | None = None - """The OAuth refresh token for the connected account.""" - expires_at: datetime | None = None - """The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire.""" - scopes: list[str] | None = None - """The OAuth scopes granted for this connection.""" - state: ConnectedAccountInputState | None = None - """Explicitly set the state of the connected account. When omitted, the state is derived from the token combination provided.""" - - @classmethod - def from_dict(cls, data: dict[str, Any]) -> ConnectedAccountInput: - """Deserialize from a dictionary.""" - try: - return cls( - access_token=data.get("access_token"), - refresh_token=data.get("refresh_token"), - expires_at=_parse_datetime(_v_expires_at) - if (_v_expires_at := data.get("expires_at")) is not None - else None, - scopes=data.get("scopes"), - state=ConnectedAccountInputState(_v_state) - if (_v_state := data.get("state")) is not None - else None, - ) - except (KeyError, ValueError) as e: - _raise_deserialize_error("ConnectedAccountInput", e) - - def to_dict(self) -> dict[str, Any]: - """Serialize to a dictionary.""" - result: dict[str, Any] = {} - if self.access_token is not None: - result["access_token"] = self.access_token - if self.refresh_token is not None: - result["refresh_token"] = self.refresh_token - if self.expires_at is not None: - result["expires_at"] = _format_datetime(self.expires_at) - if self.scopes is not None: - result["scopes"] = self.scopes - if self.state is not None: - result["state"] = ( - self.state.value if isinstance(self.state, Enum) else self.state - ) - return result diff --git a/src/workos/pipes/models/data_integrations_list_response.py b/src/workos/pipes/models/data_integrations_list_response.py deleted file mode 100644 index a8482582a..000000000 --- a/src/workos/pipes/models/data_integrations_list_response.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file is auto-generated by oagen. Do not edit. - -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any, Literal, cast - -from workos._types import _raise_deserialize_error - -from .data_integrations_list_response_data import DataIntegrationsListResponseData - - -@dataclass(slots=True) -class DataIntegrationsListResponse: - """Data Integrations List Response model.""" - - object: Literal["list"] - """Indicates this is a list response.""" - data: list[DataIntegrationsListResponseData] - """A list of [providers](https://workos.com/docs/reference/pipes/provider), each including the legacy `connected_account` field and the additive `connected_accounts` collection.""" - - @classmethod - def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsListResponse: - """Deserialize from a dictionary.""" - try: - return cls( - object=data.get("object", "list"), - data=[ - DataIntegrationsListResponseData.from_dict( - cast(dict[str, Any], item) - ) - for item in cast(list[Any], data["data"]) - ], - ) - except (KeyError, ValueError) as e: - _raise_deserialize_error("DataIntegrationsListResponse", e) - - def to_dict(self) -> dict[str, Any]: - """Serialize to a dictionary.""" - result: dict[str, Any] = {} - result["object"] = self.object - result["data"] = [item.to_dict() for item in self.data] - return result diff --git a/src/workos/pipes/models/data_integrations_list_response_data.py b/src/workos/pipes/models/data_integrations_list_response_data.py deleted file mode 100644 index cd02d5a59..000000000 --- a/src/workos/pipes/models/data_integrations_list_response_data.py +++ /dev/null @@ -1,126 +0,0 @@ -# This file is auto-generated by oagen. Do not edit. - -from __future__ import annotations - -from dataclasses import dataclass -from enum import Enum -from typing import Any, Literal, cast - -from workos._types import _raise_deserialize_error -from workos.common.models.data_integrations_list_response_data_auth_methods import ( - DataIntegrationsListResponseDataAuthMethods, -) -from workos.common.models.data_integrations_list_response_data_ownership import ( - DataIntegrationsListResponseDataOwnership, -) - -from .data_integrations_list_response_data_connected_account import ( - DataIntegrationsListResponseDataConnectedAccount, -) - - -@dataclass(slots=True) -class DataIntegrationsListResponseData: - """Data Integrations List Response Data model.""" - - object: Literal["data_provider"] - """Distinguishes the data provider object.""" - id: str - """The unique identifier of the provider.""" - name: str - """The display name of the provider (e.g., "GitHub", "Slack").""" - description: str | None - """A description of the provider explaining how it will be used, if configured.""" - slug: str - """The slug identifier used in API calls (e.g., `github`, `slack`, `notion`).""" - integration_type: str - """The type of integration (e.g., `github`, `slack`).""" - credentials_type: str - """The type of credentials used by the provider (e.g., `oauth2`).""" - scopes: list[str] | None - """The OAuth scopes configured for this provider, or `null` if none are configured.""" - ownership: DataIntegrationsListResponseDataOwnership - """Whether the provider is owned by a user or organization.""" - created_at: str - """The timestamp when the provider was created.""" - updated_at: str - """The timestamp when the provider was last updated.""" - connected_account: DataIntegrationsListResponseDataConnectedAccount | None - """The user's compatibility [connected account](https://workos.com/docs/reference/pipes/connected-account) for this provider, or `null` when the compatibility slot is empty. This legacy field never selects a standard connection.""" - connected_accounts: list[DataIntegrationsListResponseDataConnectedAccount] - """The user's connected accounts for this provider in the requested ownership context. This contains only the compatibility connection unless `supports_multiple_connections` is `true`.""" - auth_methods: list[DataIntegrationsListResponseDataAuthMethods] | None = None - """The authentication methods supported by this provider (`oauth`, `api_key`, `client_credentials`, or a combination). Defaults to `["oauth"]` if absent.""" - - @classmethod - def from_dict(cls, data: dict[str, Any]) -> DataIntegrationsListResponseData: - """Deserialize from a dictionary.""" - try: - return cls( - object=data.get("object", "data_provider"), - id=data["id"], - name=data["name"], - description=data["description"], - slug=data["slug"], - integration_type=data["integration_type"], - credentials_type=data["credentials_type"], - scopes=data["scopes"], - ownership=DataIntegrationsListResponseDataOwnership(data["ownership"]), - created_at=data["created_at"], - updated_at=data["updated_at"], - connected_account=DataIntegrationsListResponseDataConnectedAccount.from_dict( - cast(dict[str, Any], _v_connected_account) - ) - if (_v_connected_account := data["connected_account"]) is not None - else None, - connected_accounts=[ - DataIntegrationsListResponseDataConnectedAccount.from_dict( - cast(dict[str, Any], item) - ) - for item in cast(list[Any], data["connected_accounts"]) - ], - auth_methods=[ - DataIntegrationsListResponseDataAuthMethods(item) - for item in cast(list[Any], _v_auth_methods) - ] - if (_v_auth_methods := data.get("auth_methods")) is not None - else None, - ) - except (KeyError, ValueError) as e: - _raise_deserialize_error("DataIntegrationsListResponseData", e) - - def to_dict(self) -> dict[str, Any]: - """Serialize to a dictionary.""" - result: dict[str, Any] = {} - result["object"] = self.object - result["id"] = self.id - result["name"] = self.name - if self.description is not None: - result["description"] = self.description - else: - result["description"] = None - result["slug"] = self.slug - result["integration_type"] = self.integration_type - result["credentials_type"] = self.credentials_type - if self.scopes is not None: - result["scopes"] = self.scopes - else: - result["scopes"] = None - result["ownership"] = ( - self.ownership.value if isinstance(self.ownership, Enum) else self.ownership - ) - result["created_at"] = self.created_at - result["updated_at"] = self.updated_at - if self.connected_account is not None: - result["connected_account"] = self.connected_account.to_dict() - else: - result["connected_account"] = None - result["connected_accounts"] = [ - item.to_dict() for item in self.connected_accounts - ] - if self.auth_methods is not None: - result["auth_methods"] = [ - item.value if isinstance(item, Enum) else item - for item in self.auth_methods - ] - return result diff --git a/src/workos/pipes/models/data_integrations_list_response_data_connected_account.py b/src/workos/pipes/models/data_integrations_list_response_data_connected_account.py deleted file mode 100644 index 51acd9818..000000000 --- a/src/workos/pipes/models/data_integrations_list_response_data_connected_account.py +++ /dev/null @@ -1,134 +0,0 @@ -# This file is auto-generated by oagen. Do not edit. - -from __future__ import annotations - -from dataclasses import dataclass -from enum import Enum -from typing import Any, Literal - -from workos._types import _raise_deserialize_error -from workos.common.models.data_integrations_list_response_data_connected_account_auth_method import ( - DataIntegrationsListResponseDataConnectedAccountAuthMethod, -) -from workos.common.models.data_integrations_list_response_data_connected_account_state import ( - DataIntegrationsListResponseDataConnectedAccountState, -) - - -@dataclass(slots=True) -class DataIntegrationsListResponseDataConnectedAccount: - """Data Integrations List Response Data Connected Account model.""" - - object: Literal["connected_account"] - """Distinguishes the connected account object.""" - id: str - """The unique identifier of the connected account.""" - user_id: str | None - """The [User](https://workos.com/docs/reference/authkit/user) identifier associated with this connection.""" - organization_id: str | None - """The [Organization](https://workos.com/docs/reference/organization) identifier associated with this connection, or `null` if not scoped to an organization.""" - scopes: list[str] - """The OAuth scopes granted for this connection.""" - state: DataIntegrationsListResponseDataConnectedAccountState - """The state of the connected account: -- `connected`: The connection is active and tokens are valid. -- `needs_reauthorization`: The user needs to reauthorize the connection, typically because required scopes have changed. -- `disconnected`: The connection has been disconnected.""" - created_at: str - """The timestamp when the connection was created.""" - updated_at: str - """The timestamp when the connection was last updated.""" - auth_method: DataIntegrationsListResponseDataConnectedAccountAuthMethod | None = ( - None - ) - """The authentication method used for this connection (`oauth`, `api_key`, or `client_credentials`). Defaults to `oauth` if absent.""" - api_key_last_4: str | None = None - """The last four characters of the API key, or `null` for OAuth connections.""" - client_id: str | None = None - """The client ID supplied for this connection. Only present when `auth_method` is `client_credentials`.""" - client_secret_last_4: str | None = None - """The last four characters of the client secret supplied for this connection, or `null` when it can't be read. Only present when `auth_method` is `client_credentials`.""" - config: dict[str, str] | None = None - """The connection-level configuration values stored for this connection — the fields the provider declares at `installation` scope, excluding any it declares as secret. Only present when `auth_method` is `client_credentials`.""" - userland_user_id: str | None = None - """Use `user_id` instead. - - .. deprecated:: This field is deprecated.""" - - @classmethod - def from_dict( - cls, data: dict[str, Any] - ) -> DataIntegrationsListResponseDataConnectedAccount: - """Deserialize from a dictionary.""" - try: - return cls( - object=data.get("object", "connected_account"), - id=data["id"], - user_id=data["user_id"], - organization_id=data["organization_id"], - scopes=data["scopes"], - state=DataIntegrationsListResponseDataConnectedAccountState( - data["state"] - ), - created_at=data["created_at"], - updated_at=data["updated_at"], - auth_method=DataIntegrationsListResponseDataConnectedAccountAuthMethod( - _v_auth_method - ) - if (_v_auth_method := data.get("auth_method")) is not None - else None, - api_key_last_4=data.get("api_key_last_4"), - client_id=data.get("client_id"), - client_secret_last_4=data.get("client_secret_last_4"), - config=data.get("config"), - userland_user_id=data.get("userlandUserId"), - ) - except (KeyError, ValueError) as e: - _raise_deserialize_error( - "DataIntegrationsListResponseDataConnectedAccount", e - ) - - def to_dict(self) -> dict[str, Any]: - """Serialize to a dictionary.""" - result: dict[str, Any] = {} - result["object"] = self.object - result["id"] = self.id - if self.user_id is not None: - result["user_id"] = self.user_id - else: - result["user_id"] = None - if self.organization_id is not None: - result["organization_id"] = self.organization_id - else: - result["organization_id"] = None - result["scopes"] = self.scopes - result["state"] = ( - self.state.value if isinstance(self.state, Enum) else self.state - ) - result["created_at"] = self.created_at - result["updated_at"] = self.updated_at - if self.auth_method is not None: - result["auth_method"] = ( - self.auth_method.value - if isinstance(self.auth_method, Enum) - else self.auth_method - ) - if self.api_key_last_4 is not None: - result["api_key_last_4"] = self.api_key_last_4 - else: - result["api_key_last_4"] = None - if self.client_id is not None: - result["client_id"] = self.client_id - else: - result["client_id"] = None - if self.client_secret_last_4 is not None: - result["client_secret_last_4"] = self.client_secret_last_4 - else: - result["client_secret_last_4"] = None - if self.config is not None: - result["config"] = self.config - if self.userland_user_id is not None: - result["userlandUserId"] = self.userland_user_id - else: - result["userlandUserId"] = None - return result diff --git a/src/workos/types/organizations_data_providers/__init__.py b/src/workos/types/organizations_data_providers/__init__.py deleted file mode 100644 index cd43f694a..000000000 --- a/src/workos/types/organizations_data_providers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# This file is auto-generated by oagen. Do not edit. - -from workos.organizations_data_providers.models import * diff --git a/tests/test_organizations_data_providers.py b/tests/test_organizations_data_providers.py deleted file mode 100644 index 062d01eff..000000000 --- a/tests/test_organizations_data_providers.py +++ /dev/null @@ -1,501 +0,0 @@ -# This file is auto-generated by oagen. Do not edit. - - -import pytest - -from tests.generated_helpers import load_fixture -from workos import AsyncWorkOSClient, WorkOSClient -from workos._errors import ( - AuthenticationError, - BadRequestError, - NotFoundError, - RateLimitExceededError, - ServerError, - UnprocessableEntityError, -) -from workos.common.models import ConnectedAccount, DataIntegrationsListResponse - - -class TestOrganizationsDataProviders: - def test_get_organization_connected_account(self, workos, httpx_mock): - httpx_mock.add_response( - json=load_fixture("connected_account.json"), - ) - result = workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - assert isinstance(result, ConnectedAccount) - assert result.object == "connected_account" - assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" - request = httpx_mock.get_request() - assert request.method == "GET" - assert request.url.path.endswith( - "/organizations/test_organization_id/connected_accounts/test_slug" - ) - - def test_get_organization_connected_account_encodes_query_params( - self, workos, httpx_mock - ): - httpx_mock.add_response(json=load_fixture("connected_account.json")) - workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", - "test_slug", - supports_multiple_connections=True, - connected_account_id="value connected_account_id/test", - ) - request = httpx_mock.get_request() - assert request.url.params["supports_multiple_connections"] == "true" - assert ( - request.url.params["connected_account_id"] - == "value connected_account_id/test" - ) - - def test_create_organization_connected_account(self, workos, httpx_mock): - httpx_mock.add_response( - json=load_fixture("connected_account.json"), - ) - result = ( - workos.organizations_data_providers.create_organization_connected_account( - "test_organization_id", "test_slug" - ) - ) - assert isinstance(result, ConnectedAccount) - assert result.object == "connected_account" - assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" - request = httpx_mock.get_request() - assert request.method == "POST" - assert request.url.path.endswith( - "/organizations/test_organization_id/connected_accounts/test_slug" - ) - - def test_update_organization_connected_account(self, workos, httpx_mock): - httpx_mock.add_response( - json=load_fixture("connected_account.json"), - ) - result = ( - workos.organizations_data_providers.update_organization_connected_account( - "test_organization_id", "test_slug" - ) - ) - assert isinstance(result, ConnectedAccount) - assert result.object == "connected_account" - assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" - request = httpx_mock.get_request() - assert request.method == "PUT" - assert request.url.path.endswith( - "/organizations/test_organization_id/connected_accounts/test_slug" - ) - - def test_update_organization_connected_account_encodes_query_params( - self, workos, httpx_mock - ): - httpx_mock.add_response(json=load_fixture("connected_account.json")) - workos.organizations_data_providers.update_organization_connected_account( - "test_organization_id", - "test_slug", - supports_multiple_connections=True, - connected_account_id="value connected_account_id/test", - ) - request = httpx_mock.get_request() - assert request.url.params["supports_multiple_connections"] == "true" - assert ( - request.url.params["connected_account_id"] - == "value connected_account_id/test" - ) - - def test_delete_organization_connected_account(self, workos, httpx_mock): - httpx_mock.add_response(status_code=204) - result = ( - workos.organizations_data_providers.delete_organization_connected_account( - "test_organization_id", "test_slug" - ) - ) - assert result is None - request = httpx_mock.get_request() - assert request.method == "DELETE" - assert request.url.path.endswith( - "/organizations/test_organization_id/connected_accounts/test_slug" - ) - - def test_delete_organization_connected_account_encodes_query_params( - self, workos, httpx_mock - ): - httpx_mock.add_response(status_code=204) - workos.organizations_data_providers.delete_organization_connected_account( - "test_organization_id", - "test_slug", - supports_multiple_connections=True, - connected_account_id="value connected_account_id/test", - ) - request = httpx_mock.get_request() - assert request.url.params["supports_multiple_connections"] == "true" - assert ( - request.url.params["connected_account_id"] - == "value connected_account_id/test" - ) - - def test_list_organization_data_providers(self, workos, httpx_mock): - httpx_mock.add_response( - json=load_fixture("data_integrations_list_response.json"), - ) - result = workos.organizations_data_providers.list_organization_data_providers( - "test_organization_id" - ) - assert isinstance(result, DataIntegrationsListResponse) - assert result.object == "list" - request = httpx_mock.get_request() - assert request.method == "GET" - assert request.url.path.endswith( - "/organizations/test_organization_id/data_providers" - ) - - def test_list_organization_data_providers_encodes_query_params( - self, workos, httpx_mock - ): - httpx_mock.add_response( - json=load_fixture("data_integrations_list_response.json") - ) - workos.organizations_data_providers.list_organization_data_providers( - "test_organization_id", supports_multiple_connections=True - ) - request = httpx_mock.get_request() - assert request.url.params["supports_multiple_connections"] == "true" - - def test_get_organization_connected_account_with_request_options( - self, workos, httpx_mock - ): - httpx_mock.add_response(json=load_fixture("connected_account.json")) - workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", - "test_slug", - request_options={"extra_headers": {"X-Custom": "value"}}, - ) - request = httpx_mock.get_request() - assert request.headers["X-Custom"] == "value" - - def test_get_organization_connected_account_unauthorized(self, workos, httpx_mock): - httpx_mock.add_response( - status_code=401, - json={"message": "Unauthorized"}, - ) - with pytest.raises(AuthenticationError): - workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - - def test_get_organization_connected_account_not_found(self, httpx_mock): - workos = WorkOSClient( - api_key="sk_test_123", client_id="client_test", max_retries=0 - ) - try: - httpx_mock.add_response(status_code=404, json={"message": "Not found"}) - with pytest.raises(NotFoundError): - workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - finally: - workos.close() - - def test_get_organization_connected_account_rate_limited(self, httpx_mock): - workos = WorkOSClient( - api_key="sk_test_123", client_id="client_test", max_retries=0 - ) - try: - httpx_mock.add_response( - status_code=429, - headers={"Retry-After": "0"}, - json={"message": "Slow down"}, - ) - with pytest.raises(RateLimitExceededError): - workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - finally: - workos.close() - - def test_get_organization_connected_account_server_error(self, httpx_mock): - workos = WorkOSClient( - api_key="sk_test_123", client_id="client_test", max_retries=0 - ) - try: - httpx_mock.add_response(status_code=500, json={"message": "Server error"}) - with pytest.raises(ServerError): - workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - finally: - workos.close() - - def test_get_organization_connected_account_bad_request(self, httpx_mock): - workos = WorkOSClient( - api_key="sk_test_123", client_id="client_test", max_retries=0 - ) - try: - httpx_mock.add_response(status_code=400, json={"message": "Bad request"}) - with pytest.raises(BadRequestError): - workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - finally: - workos.close() - - def test_get_organization_connected_account_unprocessable(self, httpx_mock): - workos = WorkOSClient( - api_key="sk_test_123", client_id="client_test", max_retries=0 - ) - try: - httpx_mock.add_response(status_code=422, json={"message": "Unprocessable"}) - with pytest.raises(UnprocessableEntityError): - workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - finally: - workos.close() - - -class TestAsyncOrganizationsDataProviders: - @pytest.mark.asyncio - async def test_get_organization_connected_account(self, async_workos, httpx_mock): - httpx_mock.add_response(json=load_fixture("connected_account.json")) - result = await async_workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - assert isinstance(result, ConnectedAccount) - assert result.object == "connected_account" - assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" - request = httpx_mock.get_request() - assert request.method == "GET" - assert request.url.path.endswith( - "/organizations/test_organization_id/connected_accounts/test_slug" - ) - - @pytest.mark.asyncio - async def test_get_organization_connected_account_encodes_query_params( - self, async_workos, httpx_mock - ): - httpx_mock.add_response(json=load_fixture("connected_account.json")) - await async_workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", - "test_slug", - supports_multiple_connections=True, - connected_account_id="value connected_account_id/test", - ) - request = httpx_mock.get_request() - assert request.url.params["supports_multiple_connections"] == "true" - assert ( - request.url.params["connected_account_id"] - == "value connected_account_id/test" - ) - - @pytest.mark.asyncio - async def test_create_organization_connected_account( - self, async_workos, httpx_mock - ): - httpx_mock.add_response(json=load_fixture("connected_account.json")) - result = await async_workos.organizations_data_providers.create_organization_connected_account( - "test_organization_id", "test_slug" - ) - assert isinstance(result, ConnectedAccount) - assert result.object == "connected_account" - assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" - request = httpx_mock.get_request() - assert request.method == "POST" - assert request.url.path.endswith( - "/organizations/test_organization_id/connected_accounts/test_slug" - ) - - @pytest.mark.asyncio - async def test_update_organization_connected_account( - self, async_workos, httpx_mock - ): - httpx_mock.add_response(json=load_fixture("connected_account.json")) - result = await async_workos.organizations_data_providers.update_organization_connected_account( - "test_organization_id", "test_slug" - ) - assert isinstance(result, ConnectedAccount) - assert result.object == "connected_account" - assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" - request = httpx_mock.get_request() - assert request.method == "PUT" - assert request.url.path.endswith( - "/organizations/test_organization_id/connected_accounts/test_slug" - ) - - @pytest.mark.asyncio - async def test_update_organization_connected_account_encodes_query_params( - self, async_workos, httpx_mock - ): - httpx_mock.add_response(json=load_fixture("connected_account.json")) - await async_workos.organizations_data_providers.update_organization_connected_account( - "test_organization_id", - "test_slug", - supports_multiple_connections=True, - connected_account_id="value connected_account_id/test", - ) - request = httpx_mock.get_request() - assert request.url.params["supports_multiple_connections"] == "true" - assert ( - request.url.params["connected_account_id"] - == "value connected_account_id/test" - ) - - @pytest.mark.asyncio - async def test_delete_organization_connected_account( - self, async_workos, httpx_mock - ): - httpx_mock.add_response(status_code=204) - result = await async_workos.organizations_data_providers.delete_organization_connected_account( - "test_organization_id", "test_slug" - ) - assert result is None - request = httpx_mock.get_request() - assert request.method == "DELETE" - assert request.url.path.endswith( - "/organizations/test_organization_id/connected_accounts/test_slug" - ) - - @pytest.mark.asyncio - async def test_delete_organization_connected_account_encodes_query_params( - self, async_workos, httpx_mock - ): - httpx_mock.add_response(status_code=204) - await async_workos.organizations_data_providers.delete_organization_connected_account( - "test_organization_id", - "test_slug", - supports_multiple_connections=True, - connected_account_id="value connected_account_id/test", - ) - request = httpx_mock.get_request() - assert request.url.params["supports_multiple_connections"] == "true" - assert ( - request.url.params["connected_account_id"] - == "value connected_account_id/test" - ) - - @pytest.mark.asyncio - async def test_list_organization_data_providers(self, async_workos, httpx_mock): - httpx_mock.add_response( - json=load_fixture("data_integrations_list_response.json") - ) - result = await async_workos.organizations_data_providers.list_organization_data_providers( - "test_organization_id" - ) - assert isinstance(result, DataIntegrationsListResponse) - assert result.object == "list" - request = httpx_mock.get_request() - assert request.method == "GET" - assert request.url.path.endswith( - "/organizations/test_organization_id/data_providers" - ) - - @pytest.mark.asyncio - async def test_list_organization_data_providers_encodes_query_params( - self, async_workos, httpx_mock - ): - httpx_mock.add_response( - json=load_fixture("data_integrations_list_response.json") - ) - await ( - async_workos.organizations_data_providers.list_organization_data_providers( - "test_organization_id", supports_multiple_connections=True - ) - ) - request = httpx_mock.get_request() - assert request.url.params["supports_multiple_connections"] == "true" - - @pytest.mark.asyncio - async def test_get_organization_connected_account_with_request_options( - self, async_workos, httpx_mock - ): - httpx_mock.add_response(json=load_fixture("connected_account.json")) - await async_workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", - "test_slug", - request_options={"extra_headers": {"X-Custom": "value"}}, - ) - request = httpx_mock.get_request() - assert request.headers["X-Custom"] == "value" - - @pytest.mark.asyncio - async def test_get_organization_connected_account_unauthorized( - self, async_workos, httpx_mock - ): - httpx_mock.add_response(status_code=401, json={"message": "Unauthorized"}) - with pytest.raises(AuthenticationError): - await async_workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - - @pytest.mark.asyncio - async def test_get_organization_connected_account_not_found(self, httpx_mock): - workos = AsyncWorkOSClient( - api_key="sk_test_123", client_id="client_test", max_retries=0 - ) - try: - httpx_mock.add_response(status_code=404, json={"message": "Not found"}) - with pytest.raises(NotFoundError): - await workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - finally: - await workos.close() - - @pytest.mark.asyncio - async def test_get_organization_connected_account_rate_limited(self, httpx_mock): - workos = AsyncWorkOSClient( - api_key="sk_test_123", client_id="client_test", max_retries=0 - ) - try: - httpx_mock.add_response( - status_code=429, - headers={"Retry-After": "0"}, - json={"message": "Slow down"}, - ) - with pytest.raises(RateLimitExceededError): - await workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - finally: - await workos.close() - - @pytest.mark.asyncio - async def test_get_organization_connected_account_server_error(self, httpx_mock): - workos = AsyncWorkOSClient( - api_key="sk_test_123", client_id="client_test", max_retries=0 - ) - try: - httpx_mock.add_response(status_code=500, json={"message": "Server error"}) - with pytest.raises(ServerError): - await workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - finally: - await workos.close() - - @pytest.mark.asyncio - async def test_get_organization_connected_account_bad_request(self, httpx_mock): - workos = AsyncWorkOSClient( - api_key="sk_test_123", client_id="client_test", max_retries=0 - ) - try: - httpx_mock.add_response(status_code=400, json={"message": "Bad request"}) - with pytest.raises(BadRequestError): - await workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - finally: - await workos.close() - - @pytest.mark.asyncio - async def test_get_organization_connected_account_unprocessable(self, httpx_mock): - workos = AsyncWorkOSClient( - api_key="sk_test_123", client_id="client_test", max_retries=0 - ) - try: - httpx_mock.add_response(status_code=422, json={"message": "Unprocessable"}) - with pytest.raises(UnprocessableEntityError): - await workos.organizations_data_providers.get_organization_connected_account( - "test_organization_id", "test_slug" - ) - finally: - await workos.close() diff --git a/tests/test_organizations_data_providers_models_round_trip.py b/tests/test_organizations_data_providers_models_round_trip.py deleted file mode 100644 index 3d6733a93..000000000 --- a/tests/test_organizations_data_providers_models_round_trip.py +++ /dev/null @@ -1,441 +0,0 @@ -# This file is auto-generated by oagen. Do not edit. - -"""Model round-trip tests: from_dict(to_dict()) preserves data.""" - -from tests.generated_helpers import load_fixture -from workos.organizations_data_providers.models import ( - DataIntegrationsListResponse, - DataIntegrationsListResponseData, - DataIntegrationsListResponseDataConnectedAccount, -) - - -class TestModelRoundTrip: - def test_data_integrations_list_response_round_trip(self): - data = load_fixture("data_integrations_list_response.json") - instance = DataIntegrationsListResponse.from_dict(data) - serialized = instance.to_dict() - assert serialized == data - restored = DataIntegrationsListResponse.from_dict(serialized) - assert restored.to_dict() == serialized - - def test_data_integrations_list_response_minimal_payload(self): - data = { - "object": "list", - "data": [ - { - "object": "data_provider", - "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", - "name": "GitHub", - "description": "Connect your GitHub account to access repositories.", - "slug": "github", - "integration_type": "github", - "credentials_type": "oauth2", - "scopes": ["repo", "user:email"], - "auth_methods": ["oauth"], - "connection_owner": "user", - "ownership": "userland_user", - "created_at": "2024-01-15T10:30:00.000Z", - "updated_at": "2024-01-15T10:30:00.000Z", - "connected_account": { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "connection_role": "compatibility", - "account_identifier": "workspace_123", - "account_display_name": "Acme production", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - }, - "connected_accounts": [ - { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "connection_role": "compatibility", - "account_identifier": "workspace_123", - "account_display_name": "Acme production", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": { - "instance_url": "https://example.my.salesforce.com" - }, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - ], - } - ], - } - instance = DataIntegrationsListResponse.from_dict(data) - serialized = instance.to_dict() - assert serialized["object"] == data["object"] - assert serialized["data"] == data["data"] - - def test_data_integrations_list_response_data_round_trip(self): - data = load_fixture("data_integrations_list_response_data.json") - instance = DataIntegrationsListResponseData.from_dict(data) - serialized = instance.to_dict() - assert serialized == data - restored = DataIntegrationsListResponseData.from_dict(serialized) - assert restored.to_dict() == serialized - - def test_data_integrations_list_response_data_minimal_payload(self): - data = { - "object": "data_provider", - "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", - "name": "GitHub", - "description": None, - "slug": "github", - "integration_type": "github", - "credentials_type": "oauth2", - "scopes": None, - "connection_owner": "user", - "ownership": "userland_user", - "created_at": "2024-01-15T10:30:00.000Z", - "updated_at": "2024-01-15T10:30:00.000Z", - "connected_account": None, - "connected_accounts": [ - { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "connection_role": "compatibility", - "account_identifier": "workspace_123", - "account_display_name": "Acme production", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - ], - } - instance = DataIntegrationsListResponseData.from_dict(data) - serialized = instance.to_dict() - assert serialized["object"] == data["object"] - assert serialized["id"] == data["id"] - assert serialized["name"] == data["name"] - assert serialized["description"] == data["description"] - assert serialized["slug"] == data["slug"] - assert serialized["integration_type"] == data["integration_type"] - assert serialized["credentials_type"] == data["credentials_type"] - assert serialized["scopes"] == data["scopes"] - assert serialized["connection_owner"] == data["connection_owner"] - assert serialized["ownership"] == data["ownership"] - assert serialized["created_at"] == data["created_at"] - assert serialized["updated_at"] == data["updated_at"] - assert serialized["connected_account"] == data["connected_account"] - assert serialized["connected_accounts"] == data["connected_accounts"] - - def test_data_integrations_list_response_data_omits_absent_optional_non_nullable_fields( - self, - ): - data = { - "object": "data_provider", - "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", - "name": "GitHub", - "description": "Connect your GitHub account to access repositories.", - "slug": "github", - "integration_type": "github", - "credentials_type": "oauth2", - "scopes": ["repo", "user:email"], - "connection_owner": "user", - "ownership": "userland_user", - "created_at": "2024-01-15T10:30:00.000Z", - "updated_at": "2024-01-15T10:30:00.000Z", - "connected_account": { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "connection_role": "compatibility", - "account_identifier": "workspace_123", - "account_display_name": "Acme production", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - }, - "connected_accounts": [ - { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "connection_role": "compatibility", - "account_identifier": "workspace_123", - "account_display_name": "Acme production", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - ], - } - instance = DataIntegrationsListResponseData.from_dict(data) - serialized = instance.to_dict() - assert "auth_methods" not in serialized - - def test_data_integrations_list_response_data_preserves_nullable_fields(self): - data = { - "object": "data_provider", - "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", - "name": "GitHub", - "description": None, - "slug": "github", - "integration_type": "github", - "credentials_type": "oauth2", - "scopes": None, - "auth_methods": ["oauth"], - "connection_owner": "user", - "ownership": "userland_user", - "created_at": "2024-01-15T10:30:00.000Z", - "updated_at": "2024-01-15T10:30:00.000Z", - "connected_account": None, - "connected_accounts": [ - { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "connection_role": "compatibility", - "account_identifier": "workspace_123", - "account_display_name": "Acme production", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - ], - } - instance = DataIntegrationsListResponseData.from_dict(data) - serialized = instance.to_dict() - assert serialized["description"] is None - assert serialized["scopes"] is None - assert serialized["connected_account"] is None - - def test_data_integrations_list_response_data_round_trips_unknown_enum_values(self): - data = { - "object": "data_provider", - "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", - "name": "GitHub", - "description": "Connect your GitHub account to access repositories.", - "slug": "github", - "integration_type": "github", - "credentials_type": "oauth2", - "scopes": ["repo", "user:email"], - "auth_methods": ["oauth"], - "connection_owner": "unexpected_data_integrations_list_response_data_connection_owner", - "ownership": "userland_user", - "created_at": "2024-01-15T10:30:00.000Z", - "updated_at": "2024-01-15T10:30:00.000Z", - "connected_account": { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "connection_role": "compatibility", - "account_identifier": "workspace_123", - "account_display_name": "Acme production", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - }, - "connected_accounts": [ - { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "connection_role": "compatibility", - "account_identifier": "workspace_123", - "account_display_name": "Acme production", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - ], - } - instance = DataIntegrationsListResponseData.from_dict(data) - assert instance.to_dict() == data - - def test_data_integrations_list_response_data_connected_account_round_trip(self): - data = load_fixture( - "data_integrations_list_response_data_connected_account.json" - ) - instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) - serialized = instance.to_dict() - assert serialized == data - restored = DataIntegrationsListResponseDataConnectedAccount.from_dict( - serialized - ) - assert restored.to_dict() == serialized - - def test_data_integrations_list_response_data_connected_account_minimal_payload( - self, - ): - data = { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "connection_role": "compatibility", - "account_identifier": None, - "account_display_name": None, - "user_id": None, - "organization_id": None, - "scopes": ["repo", "user:email"], - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": None, - } - instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) - serialized = instance.to_dict() - assert serialized["object"] == data["object"] - assert serialized["id"] == data["id"] - assert serialized["connection_role"] == data["connection_role"] - assert serialized["account_identifier"] == data["account_identifier"] - assert serialized["account_display_name"] == data["account_display_name"] - assert serialized["user_id"] == data["user_id"] - assert serialized["organization_id"] == data["organization_id"] - assert serialized["scopes"] == data["scopes"] - assert serialized["state"] == data["state"] - assert serialized["created_at"] == data["created_at"] - assert serialized["updated_at"] == data["updated_at"] - assert serialized["userlandUserId"] == data["userlandUserId"] - - def test_data_integrations_list_response_data_connected_account_omits_absent_optional_non_nullable_fields( - self, - ): - data = { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "connection_role": "compatibility", - "account_identifier": "workspace_123", - "account_display_name": "Acme production", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) - serialized = instance.to_dict() - assert "auth_method" not in serialized - assert "config" not in serialized - - def test_data_integrations_list_response_data_connected_account_preserves_nullable_fields( - self, - ): - data = { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "connection_role": "compatibility", - "account_identifier": None, - "account_display_name": None, - "user_id": None, - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": None, - "client_secret_last_4": None, - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": None, - } - instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) - serialized = instance.to_dict() - assert serialized["account_identifier"] is None - assert serialized["account_display_name"] is None - assert serialized["user_id"] is None - assert serialized["organization_id"] is None - assert serialized["api_key_last_4"] is None - assert serialized["client_id"] is None - assert serialized["client_secret_last_4"] is None - assert serialized["userlandUserId"] is None - - def test_data_integrations_list_response_data_connected_account_round_trips_unknown_enum_values( - self, - ): - data = { - "object": "connected_account", - "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", - "connection_role": "unexpected_data_integrations_list_response_data_connected_account_connection_role", - "account_identifier": "workspace_123", - "account_display_name": "Acme production", - "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", - "organization_id": None, - "scopes": ["repo", "user:email"], - "auth_method": "oauth", - "api_key_last_4": None, - "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", - "client_secret_last_4": "cdef", - "config": {"instance_url": "https://example.my.salesforce.com"}, - "state": "connected", - "created_at": "2024-01-16T14:20:00.000Z", - "updated_at": "2024-01-16T14:20:00.000Z", - "userlandUserId": "test_userlandUserId", - } - instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) - assert instance.to_dict() == data diff --git a/tests/test_pipes.py b/tests/test_pipes.py index 35f3cf8e9..a4b6d3c5e 100644 --- a/tests/test_pipes.py +++ b/tests/test_pipes.py @@ -227,6 +227,142 @@ def test_get_access_token(self, workos, httpx_mock): body = json.loads(request.content) assert body["user_id"] == "test_user_id" + def test_get_organization_connected_account(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("connected_account.json"), + ) + result = workos.pipes.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert isinstance(result, ConnectedAccount) + assert result.object == "connected_account" + assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + def test_get_organization_connected_account_encodes_query_params( + self, workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + workos.pipes.get_organization_connected_account( + "test_organization_id", + "test_slug", + supports_multiple_connections=True, + connected_account_id="value connected_account_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) + + def test_create_organization_connected_account(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("connected_account.json"), + ) + result = workos.pipes.create_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert isinstance(result, ConnectedAccount) + assert result.object == "connected_account" + assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + def test_update_organization_connected_account(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("connected_account.json"), + ) + result = workos.pipes.update_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert isinstance(result, ConnectedAccount) + assert result.object == "connected_account" + assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "PUT" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + def test_update_organization_connected_account_encodes_query_params( + self, workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + workos.pipes.update_organization_connected_account( + "test_organization_id", + "test_slug", + supports_multiple_connections=True, + connected_account_id="value connected_account_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) + + def test_delete_organization_connected_account(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = workos.pipes.delete_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + def test_delete_organization_connected_account_encodes_query_params( + self, workos, httpx_mock + ): + httpx_mock.add_response(status_code=204) + workos.pipes.delete_organization_connected_account( + "test_organization_id", + "test_slug", + supports_multiple_connections=True, + connected_account_id="value connected_account_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) + + def test_list_organization_data_providers(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("data_integrations_list_response.json"), + ) + result = workos.pipes.list_organization_data_providers("test_organization_id") + assert isinstance(result, DataIntegrationsListResponse) + assert result.object == "list" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/organizations/test_organization_id/data_providers" + ) + + def test_list_organization_data_providers_encodes_query_params( + self, workos, httpx_mock + ): + httpx_mock.add_response( + json=load_fixture("data_integrations_list_response.json") + ) + workos.pipes.list_organization_data_providers( + "test_organization_id", supports_multiple_connections=True + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + def test_get_user_connected_account(self, workos, httpx_mock): httpx_mock.add_response( json=load_fixture("connected_account.json"), @@ -642,6 +778,153 @@ async def test_get_access_token(self, async_workos, httpx_mock): assert request.method == "POST" assert request.url.path.endswith("/data-integrations/test_provider/token") + @pytest.mark.asyncio + async def test_get_organization_connected_account(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + result = await async_workos.pipes.get_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert isinstance(result, ConnectedAccount) + assert result.object == "connected_account" + assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + @pytest.mark.asyncio + async def test_get_organization_connected_account_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + await async_workos.pipes.get_organization_connected_account( + "test_organization_id", + "test_slug", + supports_multiple_connections=True, + connected_account_id="value connected_account_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) + + @pytest.mark.asyncio + async def test_create_organization_connected_account( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + result = await async_workos.pipes.create_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert isinstance(result, ConnectedAccount) + assert result.object == "connected_account" + assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + @pytest.mark.asyncio + async def test_update_organization_connected_account( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + result = await async_workos.pipes.update_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert isinstance(result, ConnectedAccount) + assert result.object == "connected_account" + assert result.id == "data_installation_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "PUT" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + @pytest.mark.asyncio + async def test_update_organization_connected_account_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + await async_workos.pipes.update_organization_connected_account( + "test_organization_id", + "test_slug", + supports_multiple_connections=True, + connected_account_id="value connected_account_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) + + @pytest.mark.asyncio + async def test_delete_organization_connected_account( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(status_code=204) + result = await async_workos.pipes.delete_organization_connected_account( + "test_organization_id", "test_slug" + ) + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith( + "/organizations/test_organization_id/connected_accounts/test_slug" + ) + + @pytest.mark.asyncio + async def test_delete_organization_connected_account_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(status_code=204) + await async_workos.pipes.delete_organization_connected_account( + "test_organization_id", + "test_slug", + supports_multiple_connections=True, + connected_account_id="value connected_account_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + assert ( + request.url.params["connected_account_id"] + == "value connected_account_id/test" + ) + + @pytest.mark.asyncio + async def test_list_organization_data_providers(self, async_workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("data_integrations_list_response.json") + ) + result = await async_workos.pipes.list_organization_data_providers( + "test_organization_id" + ) + assert isinstance(result, DataIntegrationsListResponse) + assert result.object == "list" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith( + "/organizations/test_organization_id/data_providers" + ) + + @pytest.mark.asyncio + async def test_list_organization_data_providers_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response( + json=load_fixture("data_integrations_list_response.json") + ) + await async_workos.pipes.list_organization_data_providers( + "test_organization_id", supports_multiple_connections=True + ) + request = httpx_mock.get_request() + assert request.url.params["supports_multiple_connections"] == "true" + @pytest.mark.asyncio async def test_get_user_connected_account(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("connected_account.json")) diff --git a/tests/test_pipes_models_round_trip.py b/tests/test_pipes_models_round_trip.py index 2ee1f2f07..56ddf3f38 100644 --- a/tests/test_pipes_models_round_trip.py +++ b/tests/test_pipes_models_round_trip.py @@ -17,6 +17,9 @@ DataIntegrationCredentialsResponseCredential, DataIntegrationCustomProvider, DataIntegrationInstallation, + DataIntegrationsListResponse, + DataIntegrationsListResponseData, + DataIntegrationsListResponseDataConnectedAccount, UpdateCustomProviderDefinition, ) @@ -533,6 +536,308 @@ def test_connected_account_round_trips_unknown_enum_values(self): instance = ConnectedAccount.from_dict(data) assert instance.to_dict() == data + def test_data_integrations_list_response_round_trip(self): + data = load_fixture("data_integrations_list_response.json") + instance = DataIntegrationsListResponse.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationsListResponse.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integrations_list_response_minimal_payload(self): + data = { + "object": "list", + "data": [ + { + "object": "data_provider", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "name": "GitHub", + "description": "Connect your GitHub account to access repositories.", + "slug": "github", + "integration_type": "github", + "credentials_type": "oauth2", + "scopes": ["repo", "user:email"], + "auth_methods": ["oauth"], + "connection_owner": "user", + "ownership": "userland_user", + "created_at": "2024-01-15T10:30:00.000Z", + "updated_at": "2024-01-15T10:30:00.000Z", + "connected_account": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + }, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": { + "instance_url": "https://example.my.salesforce.com" + }, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], + } + ], + } + instance = DataIntegrationsListResponse.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["data"] == data["data"] + + def test_data_integrations_list_response_data_round_trip(self): + data = load_fixture("data_integrations_list_response_data.json") + instance = DataIntegrationsListResponseData.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationsListResponseData.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integrations_list_response_data_minimal_payload(self): + data = { + "object": "data_provider", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "name": "GitHub", + "description": None, + "slug": "github", + "integration_type": "github", + "credentials_type": "oauth2", + "scopes": None, + "connection_owner": "user", + "ownership": "userland_user", + "created_at": "2024-01-15T10:30:00.000Z", + "updated_at": "2024-01-15T10:30:00.000Z", + "connected_account": None, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], + } + instance = DataIntegrationsListResponseData.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["name"] == data["name"] + assert serialized["description"] == data["description"] + assert serialized["slug"] == data["slug"] + assert serialized["integration_type"] == data["integration_type"] + assert serialized["credentials_type"] == data["credentials_type"] + assert serialized["scopes"] == data["scopes"] + assert serialized["connection_owner"] == data["connection_owner"] + assert serialized["ownership"] == data["ownership"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + assert serialized["connected_account"] == data["connected_account"] + assert serialized["connected_accounts"] == data["connected_accounts"] + + def test_data_integrations_list_response_data_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "object": "data_provider", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "name": "GitHub", + "description": "Connect your GitHub account to access repositories.", + "slug": "github", + "integration_type": "github", + "credentials_type": "oauth2", + "scopes": ["repo", "user:email"], + "connection_owner": "user", + "ownership": "userland_user", + "created_at": "2024-01-15T10:30:00.000Z", + "updated_at": "2024-01-15T10:30:00.000Z", + "connected_account": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + }, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], + } + instance = DataIntegrationsListResponseData.from_dict(data) + serialized = instance.to_dict() + assert "auth_methods" not in serialized + + def test_data_integrations_list_response_data_preserves_nullable_fields(self): + data = { + "object": "data_provider", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "name": "GitHub", + "description": None, + "slug": "github", + "integration_type": "github", + "credentials_type": "oauth2", + "scopes": None, + "auth_methods": ["oauth"], + "connection_owner": "user", + "ownership": "userland_user", + "created_at": "2024-01-15T10:30:00.000Z", + "updated_at": "2024-01-15T10:30:00.000Z", + "connected_account": None, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], + } + instance = DataIntegrationsListResponseData.from_dict(data) + serialized = instance.to_dict() + assert serialized["description"] is None + assert serialized["scopes"] is None + assert serialized["connected_account"] is None + + def test_data_integrations_list_response_data_round_trips_unknown_enum_values(self): + data = { + "object": "data_provider", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "name": "GitHub", + "description": "Connect your GitHub account to access repositories.", + "slug": "github", + "integration_type": "github", + "credentials_type": "oauth2", + "scopes": ["repo", "user:email"], + "auth_methods": ["oauth"], + "connection_owner": "unexpected_data_integrations_list_response_data_connection_owner", + "ownership": "userland_user", + "created_at": "2024-01-15T10:30:00.000Z", + "updated_at": "2024-01-15T10:30:00.000Z", + "connected_account": { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + }, + "connected_accounts": [ + { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + ], + } + instance = DataIntegrationsListResponseData.from_dict(data) + assert instance.to_dict() == data + def test_data_integration_credentials_response_credential_round_trip(self): data = load_fixture("data_integration_credentials_response_credential.json") instance = DataIntegrationCredentialsResponseCredential.from_dict(data) @@ -778,3 +1083,130 @@ def test_data_integration_custom_provider_round_trips_unknown_enum_values(self): } instance = DataIntegrationCustomProvider.from_dict(data) assert instance.to_dict() == data + + def test_data_integrations_list_response_data_connected_account_round_trip(self): + data = load_fixture( + "data_integrations_list_response_data_connected_account.json" + ) + instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationsListResponseDataConnectedAccount.from_dict( + serialized + ) + assert restored.to_dict() == serialized + + def test_data_integrations_list_response_data_connected_account_minimal_payload( + self, + ): + data = { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": None, + "account_display_name": None, + "user_id": None, + "organization_id": None, + "scopes": ["repo", "user:email"], + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": None, + } + instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["connection_role"] == data["connection_role"] + assert serialized["account_identifier"] == data["account_identifier"] + assert serialized["account_display_name"] == data["account_display_name"] + assert serialized["user_id"] == data["user_id"] + assert serialized["organization_id"] == data["organization_id"] + assert serialized["scopes"] == data["scopes"] + assert serialized["state"] == data["state"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + assert serialized["userlandUserId"] == data["userlandUserId"] + + def test_data_integrations_list_response_data_connected_account_omits_absent_optional_non_nullable_fields( + self, + ): + data = { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) + serialized = instance.to_dict() + assert "auth_method" not in serialized + assert "config" not in serialized + + def test_data_integrations_list_response_data_connected_account_preserves_nullable_fields( + self, + ): + data = { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "compatibility", + "account_identifier": None, + "account_display_name": None, + "user_id": None, + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": None, + "client_secret_last_4": None, + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": None, + } + instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) + serialized = instance.to_dict() + assert serialized["account_identifier"] is None + assert serialized["account_display_name"] is None + assert serialized["user_id"] is None + assert serialized["organization_id"] is None + assert serialized["api_key_last_4"] is None + assert serialized["client_id"] is None + assert serialized["client_secret_last_4"] is None + assert serialized["userlandUserId"] is None + + def test_data_integrations_list_response_data_connected_account_round_trips_unknown_enum_values( + self, + ): + data = { + "object": "connected_account", + "id": "data_installation_01EHZNVPK3SFK441A1RGBFSHRT", + "connection_role": "unexpected_data_integrations_list_response_data_connected_account_connection_role", + "account_identifier": "workspace_123", + "account_display_name": "Acme production", + "user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT", + "organization_id": None, + "scopes": ["repo", "user:email"], + "auth_method": "oauth", + "api_key_last_4": None, + "client_id": "3MVG9dZJodJWxft2VoStSCVwPFsx0eDcpVc", + "client_secret_last_4": "cdef", + "config": {"instance_url": "https://example.my.salesforce.com"}, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + "userlandUserId": "test_userlandUserId", + } + instance = DataIntegrationsListResponseDataConnectedAccount.from_dict(data) + assert instance.to_dict() == data From caa5501debcc30c80599de246ccd9ec5b408a277 Mon Sep 17 00:00:00 2001 From: "workos-sdk-automation[bot]" <255426317+workos-sdk-automation[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 09:34:40 -0400 Subject: [PATCH 9/9] chore(main): release 10.4.0 (#724) Co-authored-by: workos-sdk-automation[bot] <255426317+workos-sdk-automation[bot]@users.noreply.github.com> --- ...110a04e6629f048799bbf753bddc6c5f4d10c0f.md | 18 -- ...b4d419df1198eced531b9867bfbfdee80433fe2.md | 102 ---------- ...49c30cb87778129af801fadc33546518853d045.md | 39 ---- ...fe7aedb3cf7296ffb6ead1e287e844186479702.md | 5 - .release-please-manifest.json | 2 +- CHANGELOG.md | 180 ++++++++++++++++++ pyproject.toml | 2 +- uv.lock | 2 +- 8 files changed, 183 insertions(+), 167 deletions(-) delete mode 100644 .changelog-pending/2026-09-05T15-36-39-b110a04e6629f048799bbf753bddc6c5f4d10c0f.md delete mode 100644 .changelog-pending/2026-09-14T13-28-03-0b4d419df1198eced531b9867bfbfdee80433fe2.md delete mode 100644 .changelog-pending/2026-09-16T19-24-23-949c30cb87778129af801fadc33546518853d045.md delete mode 100644 .changelog-pending/2026-09-17T20-59-47-bfe7aedb3cf7296ffb6ead1e287e844186479702.md diff --git a/.changelog-pending/2026-09-05T15-36-39-b110a04e6629f048799bbf753bddc6c5f4d10c0f.md b/.changelog-pending/2026-09-05T15-36-39-b110a04e6629f048799bbf753bddc6c5f4d10c0f.md deleted file mode 100644 index d39ec0d69..000000000 --- a/.changelog-pending/2026-09-05T15-36-39-b110a04e6629f048799bbf753bddc6c5f4d10c0f.md +++ /dev/null @@ -1,18 +0,0 @@ -* [#723](https://github.com/workos/workos-python/pull/723) fix(generated): regenerate from spec - - **Features** - * **[pipes](https://workos.com/docs/reference/pipes)**: - * Added `connected_account_id` to `DataIntegrationsVendCredentialsRequest` - * Added `connected_account_id` to `DataIntegrationsGetUserTokenRequest` - * Added parameter `UserManagementDataProviders.getUserDataInstallation.connected_account_id` - * Changed errors for endpoint `GET /user_management/users/{user_id}/connected_accounts/{slug}` - * Added parameter `UserManagementDataProviders.updateUserDataInstallation.connected_account_id` - * Changed errors for endpoint `PUT /user_management/users/{user_id}/connected_accounts/{slug}` - * Added parameter `UserManagementDataProviders.deleteUserDataInstallation.connected_account_id` - * Changed errors for endpoint `DELETE /user_management/users/{user_id}/connected_accounts/{slug}` - * Added `connected_accounts` to `DataIntegrationsListResponseData` - * Changed errors for endpoint `PUT /data-integrations/{slug}` - * Changed errors for endpoint `PUT /data-integrations/{slug}/api-key` - * Changed errors for endpoint `PUT /data-integrations/{slug}/client-credentials` - * Changed errors for endpoint `POST /data-integrations/{slug}/credentials` - * Changed errors for endpoint `POST /data-integrations/{provider}/token` diff --git a/.changelog-pending/2026-09-14T13-28-03-0b4d419df1198eced531b9867bfbfdee80433fe2.md b/.changelog-pending/2026-09-14T13-28-03-0b4d419df1198eced531b9867bfbfdee80433fe2.md deleted file mode 100644 index 9b44e4497..000000000 --- a/.changelog-pending/2026-09-14T13-28-03-0b4d419df1198eced531b9867bfbfdee80433fe2.md +++ /dev/null @@ -1,102 +0,0 @@ -* [#727](https://github.com/workos/workos-python/pull/727) feat(generated): regenerate from spec (4 changes) - - **Features** - * **[admin_portal](https://workos.com/docs/reference/admin-portal)**: - * Changed the format of `PortalLinkResponse.link` - * **[agents](https://workos.com/docs/reference/agents)**: - * Added parameter `AgentsSessions.list.organization_id` - * Changed the format of `ClaimViewResponse.user_code` - * Changed the format of `AgentAdminValidateCredentialRequest.credential` - * **[api_keys](https://workos.com/docs/reference/authkit/api-keys)**: - * Changed the format of `ValidateApiKey.value` - * Changed the format of `OrganizationApiKeyWithValue.value` - * Changed the format of `UserApiKeyWithValue.value` - * **[multi_factor_auth](https://workos.com/docs/reference/authkit/mfa)**: - * Changed the format of `AuthenticationChallenge.code` - * Changed the format of `AuthenticationFactorEnrolledTotp.secret` - * Changed the format of `AuthenticationFactorEnrolledTotp.qr_code` - * Changed the format of `AuthenticationFactorEnrolledTotp.uri` - * Changed the format of `AuthenticationChallengesVerifyRequest.code` - * **[pipes](https://workos.com/docs/reference/pipes)**: - * Added `supports_multiple_connections` to `DataIntegrationsVendCredentialsRequest` - * Added `supports_multiple_connections` to `DataIntegrationsGetUserTokenRequest` - * Added parameter `UserManagementDataProviders.getUserDataInstallation.supports_multiple_connections` - * Added parameter `UserManagementDataProviders.updateUserDataInstallation.supports_multiple_connections` - * Added parameter `UserManagementDataProviders.deleteUserDataInstallation.supports_multiple_connections` - * Added parameter `UserManagementDataProviders.getUserDataIntegrations.supports_multiple_connections` - * Added enum `PipesOwnership` - * Added `user` to `CreateDataIntegrationOwnership` - * Added `user` to `DataIntegrationOwnership` - * Added endpoint `GET /data-integrations/{slug}/organization` - * Added endpoint `PUT /data-integrations/{slug}/organization` - * Added endpoint `DELETE /data-integrations/{slug}/organization` - * Added parameter `Pipes.listDataIntegrations.ownership` - * Changed errors for endpoint `GET /data-integrations` - * Changed errors for endpoint `PUT /user_management/users/{user_id}/connected_accounts/{slug}` - * Changed errors for endpoint `DELETE /user_management/users/{user_id}/connected_accounts/{slug}` - * Changed the format of `DataIntegrationCredentialsResponseCredential.value` - * Added `connection_owner` to `DataIntegrationsUpsertApiKeyRequest` - * Added `connection_owner` to `DataIntegrationsUpsertClientCredentialsRequest` - * Added enum `DataIntegrationsUpsertApiKeyRequestConnectionOwner` - * Added enum `DataIntegrationsUpsertClientCredentialsRequestConnectionOwner` - * Added model `AccountSelectionRequiredError` - * Added `connected_account_id` to `DataIntegrationsUpsertApiKeyRequest` - * Added `connected_account_id` to `DataIntegrationsUpsertClientCredentialsRequest` - * Added `ownership` to `CreateDataIntegration` - * Added `ownership` to `DataIntegration` - * Added `connection_owner` to `DataIntegrationsVendCredentialsRequest` - * Added `connection_owner` to `DataIntegrationsGetUserTokenRequest` - * Added enum `CreateDataIntegrationOwnership` - * Added enum `DataIntegrationOwnership` - * Added enum `DataIntegrationsVendCredentialsRequestConnectionOwner` - * Added enum `DataIntegrationsGetUserTokenRequestConnectionOwner` - * Changed errors for endpoint `POST /data-integrations/{slug}/credentials` - * Changed errors for endpoint `POST /data-integrations/{provider}/token` - * **[sso](https://workos.com/docs/reference/sso)**: - * Changed the format of `CreateConnectionKeyPair.key` - * Changed the format of `TokenQuery.code` - * Changed the format of `SSOLogoutAuthorizeResponse.logout_url` - * **[user_management](https://workos.com/docs/reference/authkit/user)**: - * Added model `CreateAuthkitOAuthResource` - * Added model `AuthkitOAuthResource` - * Added service `UserManagementAuthkitOAuthResources` - * Changed the format of `DeviceAuthorizationResponse.verification_uri_complete` - * Changed the format of `VerifyEmailAddress.code` - * Changed the format of `ConfirmEmailChange.code` - * Changed the format of `MagicAuth.code` - * Changed the format of `UserInvite.accept_invitation_url` - * Changed the format of `EmailVerification.code` - * Changed the format of `PasswordReset.password_reset_url` - * Changed the format of `AuthenticateResponse.authkit_authorization_code` - * Changed the format of `DeviceAuthorizationResponse.device_code` - * Changed the format of `DeviceAuthorizationResponse.user_code` - * Changed the format of `AuthorizationCodeSessionAuthenticateRequest.code` - * Changed the format of `AuthorizationCodeSessionAuthenticateRequest.code_verifier` - * Changed the format of `MagicAuthCodeSessionAuthenticateRequest.code` - * Changed the format of `EmailVerificationCodeSessionAuthenticateRequest.code` - * Changed the format of `MfaTotpSessionAuthenticateRequest.code` - * Changed the format of `RadarEmailChallengeCodeSessionAuthenticateRequest.code` - * Changed the format of `RadarSmsChallengeCodeSessionAuthenticateRequest.code` - * Changed the format of `DeviceCodeSessionAuthenticateRequest.device_code` - * Changed the format of `Invitation.accept_invitation_url` - * Changed the format of `MagicAuthSendMagicAuthCodeAndReturnResponse.code` - * **[radar](https://workos.com/docs/reference/radar)**: - * Changed the format of `RadarChallenge.code` - * **[vault](https://workos.com/docs/reference/vault)**: - * Changed the format of `CreateObjectRequest.value` - * Changed the format of `VaultObject.value` - * Changed the format of `UpdateObjectRequest.value` - - **Fixes** - * **[pipes](https://workos.com/docs/reference/pipes)**: - * Removed `userland_user` from `CreateDataIntegrationOwnership` - * Removed `userland_user` from `DataIntegrationOwnership` - * Changed errors for endpoint `POST /user_management/users/{user_id}/connected_accounts/{slug}` - * Changed errors for endpoint `PUT /data-integrations/{slug}/api-key` - * Changed errors for endpoint `PUT /data-integrations/{slug}/client-credentials` - * Changed errors for endpoint `POST /data-integrations/{slug}/credentials` - * Changed errors for endpoint `POST /data-integrations/{provider}/token` - * Changed errors for endpoint `GET /user_management/users/{user_id}/connected_accounts/{slug}` - * Changed errors for endpoint `PUT /user_management/users/{user_id}/connected_accounts/{slug}` - * Changed errors for endpoint `DELETE /user_management/users/{user_id}/connected_accounts/{slug}` - * Changed the type of `DataIntegrationInstallation.user_id` diff --git a/.changelog-pending/2026-09-16T19-24-23-949c30cb87778129af801fadc33546518853d045.md b/.changelog-pending/2026-09-16T19-24-23-949c30cb87778129af801fadc33546518853d045.md deleted file mode 100644 index fbb6a9e36..000000000 --- a/.changelog-pending/2026-09-16T19-24-23-949c30cb87778129af801fadc33546518853d045.md +++ /dev/null @@ -1,39 +0,0 @@ -* [#732](https://github.com/workos/workos-python/pull/732) fix(generated): regenerate from spec (1 change) - - **Features** - * **[multi_factor_auth](https://workos.com/docs/reference/authkit/mfa)**: - * Changed errors for endpoint `POST /auth/factors/{id}/challenge` - * **[pipes](https://workos.com/docs/reference/pipes)**: - * Added `connection_owner` to `DataIntegrationsListResponseData` - * Added enum `DataIntegrationsListResponseDataConnectionOwner` - * Added `connection_role` to `DataIntegrationsListResponseDataConnectedAccount` - * Added `account_identifier` to `DataIntegrationsListResponseDataConnectedAccount` - * Added `account_display_name` to `DataIntegrationsListResponseDataConnectedAccount` - * Added enum `DataIntegrationsListResponseDataConnectedAccountConnectionRole` - * Added `connection_role` to `DataIntegrationInstallation` - * Added `account_identifier` to `DataIntegrationInstallation` - * Added `account_display_name` to `DataIntegrationInstallation` - * Added enum `DataIntegrationInstallationConnectionRole` - * Added `connection_owner` to `DataIntegrationsGetDataIntegrationAuthorizeUrlRequest` - * Added enum `DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner` - * Added the organization connected-account operations on `Pipes`: `client.pipes.get_organization_connected_account`, `create_organization_connected_account`, `update_organization_connected_account`, `delete_organization_connected_account`, `list_organization_data_providers` - * **[connect](https://workos.com/docs/reference/workos-connect/standalone)**: - * Added `connection_role` to `ConnectedAccount` - * Added `account_identifier` to `ConnectedAccount` - * Added `account_display_name` to `ConnectedAccount` - * Added enum `ConnectedAccountConnectionRole` - * **[webhooks](https://workos.com/docs/reference/webhooks)**: - * Added `pipes.account_connection.add_failed` to `CreateWebhookEndpointEvents` - * Added `pipes.account_connection.connected` to `CreateWebhookEndpointEvents` - * Added `pipes.account_connection.connection_failed` to `CreateWebhookEndpointEvents` - * Added `pipes.account_connection.disconnected` to `CreateWebhookEndpointEvents` - * Added `pipes.account_connection.reauthorization_needed` to `CreateWebhookEndpointEvents` - * Added `pipes.account_connection.add_failed` to `UpdateWebhookEndpointEvents` - * Added `pipes.account_connection.connected` to `UpdateWebhookEndpointEvents` - * Added `pipes.account_connection.connection_failed` to `UpdateWebhookEndpointEvents` - * Added `pipes.account_connection.disconnected` to `UpdateWebhookEndpointEvents` - * Added `pipes.account_connection.reauthorization_needed` to `UpdateWebhookEndpointEvents` - - **Fixes** - * **[multi_factor_auth](https://workos.com/docs/reference/authkit/mfa)**: - * Changed errors for endpoint `POST /auth/factors/{id}/challenge` diff --git a/.changelog-pending/2026-09-17T20-59-47-bfe7aedb3cf7296ffb6ead1e287e844186479702.md b/.changelog-pending/2026-09-17T20-59-47-bfe7aedb3cf7296ffb6ead1e287e844186479702.md deleted file mode 100644 index b1e228daf..000000000 --- a/.changelog-pending/2026-09-17T20-59-47-bfe7aedb3cf7296ffb6ead1e287e844186479702.md +++ /dev/null @@ -1,5 +0,0 @@ -* [#733](https://github.com/workos/workos-python/pull/733) fix(generated): regenerate from spec (1 change) - - **Fixes** - * **[multi_factor_auth](https://workos.com/docs/reference/authkit/mfa)**: - * Changed errors for endpoint `POST /auth/factors/{id}/challenge` diff --git a/.release-please-manifest.json b/.release-please-manifest.json index cf43a83c6..04976058c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "10.3.0" + ".": "10.4.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d756d127c..e113ac379 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,185 @@ # Changelog +## [10.4.0](https://github.com/workos/workos-python/compare/v10.3.0...v10.4.0) (2026-09-18) + +### Features + +* Add optional jwt_issuer option for session token validation ([#725](https://github.com/workos/workos-python/issues/725)) ([70987c7](https://github.com/workos/workos-python/commit/70987c71f56f2d2d6a3e94fe2605aba3f0f1c8e8)) +* switch default HTTP client to httpx2 and accept a custom http_client ([#731](https://github.com/workos/workos-python/issues/731)) ([ce01fae](https://github.com/workos/workos-python/commit/ce01fae75c87d7ff86b25580d2bf02f99613a332)) + + +### Bug Fixes + +* reject empty and dot path segments before building request URLs ([#728](https://github.com/workos/workos-python/issues/728)) ([21735d2](https://github.com/workos/workos-python/commit/21735d2185dfd37e5873b079d92ad8f16a54719f)) + +* [#723](https://github.com/workos/workos-python/pull/723) fix(generated): regenerate from spec + + **Features** + * **[pipes](https://workos.com/docs/reference/pipes)**: + * Added `connected_account_id` to `DataIntegrationsVendCredentialsRequest` + * Added `connected_account_id` to `DataIntegrationsGetUserTokenRequest` + * Added parameter `UserManagementDataProviders.getUserDataInstallation.connected_account_id` + * Changed errors for endpoint `GET /user_management/users/{user_id}/connected_accounts/{slug}` + * Added parameter `UserManagementDataProviders.updateUserDataInstallation.connected_account_id` + * Changed errors for endpoint `PUT /user_management/users/{user_id}/connected_accounts/{slug}` + * Added parameter `UserManagementDataProviders.deleteUserDataInstallation.connected_account_id` + * Changed errors for endpoint `DELETE /user_management/users/{user_id}/connected_accounts/{slug}` + * Added `connected_accounts` to `DataIntegrationsListResponseData` + * Changed errors for endpoint `PUT /data-integrations/{slug}` + * Changed errors for endpoint `PUT /data-integrations/{slug}/api-key` + * Changed errors for endpoint `PUT /data-integrations/{slug}/client-credentials` + * Changed errors for endpoint `POST /data-integrations/{slug}/credentials` + * Changed errors for endpoint `POST /data-integrations/{provider}/token` + +* [#727](https://github.com/workos/workos-python/pull/727) feat(generated): regenerate from spec (4 changes) + + **Features** + * **[admin_portal](https://workos.com/docs/reference/admin-portal)**: + * Changed the format of `PortalLinkResponse.link` + * **[agents](https://workos.com/docs/reference/agents)**: + * Added parameter `AgentsSessions.list.organization_id` + * Changed the format of `ClaimViewResponse.user_code` + * Changed the format of `AgentAdminValidateCredentialRequest.credential` + * **[api_keys](https://workos.com/docs/reference/authkit/api-keys)**: + * Changed the format of `ValidateApiKey.value` + * Changed the format of `OrganizationApiKeyWithValue.value` + * Changed the format of `UserApiKeyWithValue.value` + * **[multi_factor_auth](https://workos.com/docs/reference/authkit/mfa)**: + * Changed the format of `AuthenticationChallenge.code` + * Changed the format of `AuthenticationFactorEnrolledTotp.secret` + * Changed the format of `AuthenticationFactorEnrolledTotp.qr_code` + * Changed the format of `AuthenticationFactorEnrolledTotp.uri` + * Changed the format of `AuthenticationChallengesVerifyRequest.code` + * **[pipes](https://workos.com/docs/reference/pipes)**: + * Added `supports_multiple_connections` to `DataIntegrationsVendCredentialsRequest` + * Added `supports_multiple_connections` to `DataIntegrationsGetUserTokenRequest` + * Added parameter `UserManagementDataProviders.getUserDataInstallation.supports_multiple_connections` + * Added parameter `UserManagementDataProviders.updateUserDataInstallation.supports_multiple_connections` + * Added parameter `UserManagementDataProviders.deleteUserDataInstallation.supports_multiple_connections` + * Added parameter `UserManagementDataProviders.getUserDataIntegrations.supports_multiple_connections` + * Added enum `PipesOwnership` + * Added `user` to `CreateDataIntegrationOwnership` + * Added `user` to `DataIntegrationOwnership` + * Added endpoint `GET /data-integrations/{slug}/organization` + * Added endpoint `PUT /data-integrations/{slug}/organization` + * Added endpoint `DELETE /data-integrations/{slug}/organization` + * Added parameter `Pipes.listDataIntegrations.ownership` + * Changed errors for endpoint `GET /data-integrations` + * Changed errors for endpoint `PUT /user_management/users/{user_id}/connected_accounts/{slug}` + * Changed errors for endpoint `DELETE /user_management/users/{user_id}/connected_accounts/{slug}` + * Changed the format of `DataIntegrationCredentialsResponseCredential.value` + * Added `connection_owner` to `DataIntegrationsUpsertApiKeyRequest` + * Added `connection_owner` to `DataIntegrationsUpsertClientCredentialsRequest` + * Added enum `DataIntegrationsUpsertApiKeyRequestConnectionOwner` + * Added enum `DataIntegrationsUpsertClientCredentialsRequestConnectionOwner` + * Added model `AccountSelectionRequiredError` + * Added `connected_account_id` to `DataIntegrationsUpsertApiKeyRequest` + * Added `connected_account_id` to `DataIntegrationsUpsertClientCredentialsRequest` + * Added `ownership` to `CreateDataIntegration` + * Added `ownership` to `DataIntegration` + * Added `connection_owner` to `DataIntegrationsVendCredentialsRequest` + * Added `connection_owner` to `DataIntegrationsGetUserTokenRequest` + * Added enum `CreateDataIntegrationOwnership` + * Added enum `DataIntegrationOwnership` + * Added enum `DataIntegrationsVendCredentialsRequestConnectionOwner` + * Added enum `DataIntegrationsGetUserTokenRequestConnectionOwner` + * Changed errors for endpoint `POST /data-integrations/{slug}/credentials` + * Changed errors for endpoint `POST /data-integrations/{provider}/token` + * **[sso](https://workos.com/docs/reference/sso)**: + * Changed the format of `CreateConnectionKeyPair.key` + * Changed the format of `TokenQuery.code` + * Changed the format of `SSOLogoutAuthorizeResponse.logout_url` + * **[user_management](https://workos.com/docs/reference/authkit/user)**: + * Added model `CreateAuthkitOAuthResource` + * Added model `AuthkitOAuthResource` + * Added service `UserManagementAuthkitOAuthResources` + * Changed the format of `DeviceAuthorizationResponse.verification_uri_complete` + * Changed the format of `VerifyEmailAddress.code` + * Changed the format of `ConfirmEmailChange.code` + * Changed the format of `MagicAuth.code` + * Changed the format of `UserInvite.accept_invitation_url` + * Changed the format of `EmailVerification.code` + * Changed the format of `PasswordReset.password_reset_url` + * Changed the format of `AuthenticateResponse.authkit_authorization_code` + * Changed the format of `DeviceAuthorizationResponse.device_code` + * Changed the format of `DeviceAuthorizationResponse.user_code` + * Changed the format of `AuthorizationCodeSessionAuthenticateRequest.code` + * Changed the format of `AuthorizationCodeSessionAuthenticateRequest.code_verifier` + * Changed the format of `MagicAuthCodeSessionAuthenticateRequest.code` + * Changed the format of `EmailVerificationCodeSessionAuthenticateRequest.code` + * Changed the format of `MfaTotpSessionAuthenticateRequest.code` + * Changed the format of `RadarEmailChallengeCodeSessionAuthenticateRequest.code` + * Changed the format of `RadarSmsChallengeCodeSessionAuthenticateRequest.code` + * Changed the format of `DeviceCodeSessionAuthenticateRequest.device_code` + * Changed the format of `Invitation.accept_invitation_url` + * Changed the format of `MagicAuthSendMagicAuthCodeAndReturnResponse.code` + * **[radar](https://workos.com/docs/reference/radar)**: + * Changed the format of `RadarChallenge.code` + * **[vault](https://workos.com/docs/reference/vault)**: + * Changed the format of `CreateObjectRequest.value` + * Changed the format of `VaultObject.value` + * Changed the format of `UpdateObjectRequest.value` + + **Fixes** + * **[pipes](https://workos.com/docs/reference/pipes)**: + * Removed `userland_user` from `CreateDataIntegrationOwnership` + * Removed `userland_user` from `DataIntegrationOwnership` + * Changed errors for endpoint `POST /user_management/users/{user_id}/connected_accounts/{slug}` + * Changed errors for endpoint `PUT /data-integrations/{slug}/api-key` + * Changed errors for endpoint `PUT /data-integrations/{slug}/client-credentials` + * Changed errors for endpoint `POST /data-integrations/{slug}/credentials` + * Changed errors for endpoint `POST /data-integrations/{provider}/token` + * Changed errors for endpoint `GET /user_management/users/{user_id}/connected_accounts/{slug}` + * Changed errors for endpoint `PUT /user_management/users/{user_id}/connected_accounts/{slug}` + * Changed errors for endpoint `DELETE /user_management/users/{user_id}/connected_accounts/{slug}` + * Changed the type of `DataIntegrationInstallation.user_id` + +* [#732](https://github.com/workos/workos-python/pull/732) fix(generated): regenerate from spec (1 change) + + **Features** + * **[multi_factor_auth](https://workos.com/docs/reference/authkit/mfa)**: + * Changed errors for endpoint `POST /auth/factors/{id}/challenge` + * **[pipes](https://workos.com/docs/reference/pipes)**: + * Added `connection_owner` to `DataIntegrationsListResponseData` + * Added enum `DataIntegrationsListResponseDataConnectionOwner` + * Added `connection_role` to `DataIntegrationsListResponseDataConnectedAccount` + * Added `account_identifier` to `DataIntegrationsListResponseDataConnectedAccount` + * Added `account_display_name` to `DataIntegrationsListResponseDataConnectedAccount` + * Added enum `DataIntegrationsListResponseDataConnectedAccountConnectionRole` + * Added `connection_role` to `DataIntegrationInstallation` + * Added `account_identifier` to `DataIntegrationInstallation` + * Added `account_display_name` to `DataIntegrationInstallation` + * Added enum `DataIntegrationInstallationConnectionRole` + * Added `connection_owner` to `DataIntegrationsGetDataIntegrationAuthorizeUrlRequest` + * Added enum `DataIntegrationsGetDataIntegrationAuthorizeUrlRequestConnectionOwner` + * Added the organization connected-account operations on `Pipes`: `client.pipes.get_organization_connected_account`, `create_organization_connected_account`, `update_organization_connected_account`, `delete_organization_connected_account`, `list_organization_data_providers` + * **[connect](https://workos.com/docs/reference/workos-connect/standalone)**: + * Added `connection_role` to `ConnectedAccount` + * Added `account_identifier` to `ConnectedAccount` + * Added `account_display_name` to `ConnectedAccount` + * Added enum `ConnectedAccountConnectionRole` + * **[webhooks](https://workos.com/docs/reference/webhooks)**: + * Added `pipes.account_connection.add_failed` to `CreateWebhookEndpointEvents` + * Added `pipes.account_connection.connected` to `CreateWebhookEndpointEvents` + * Added `pipes.account_connection.connection_failed` to `CreateWebhookEndpointEvents` + * Added `pipes.account_connection.disconnected` to `CreateWebhookEndpointEvents` + * Added `pipes.account_connection.reauthorization_needed` to `CreateWebhookEndpointEvents` + * Added `pipes.account_connection.add_failed` to `UpdateWebhookEndpointEvents` + * Added `pipes.account_connection.connected` to `UpdateWebhookEndpointEvents` + * Added `pipes.account_connection.connection_failed` to `UpdateWebhookEndpointEvents` + * Added `pipes.account_connection.disconnected` to `UpdateWebhookEndpointEvents` + * Added `pipes.account_connection.reauthorization_needed` to `UpdateWebhookEndpointEvents` + + **Fixes** + * **[multi_factor_auth](https://workos.com/docs/reference/authkit/mfa)**: + * Changed errors for endpoint `POST /auth/factors/{id}/challenge` + +* [#733](https://github.com/workos/workos-python/pull/733) fix(generated): regenerate from spec (1 change) + + **Fixes** + * **[multi_factor_auth](https://workos.com/docs/reference/authkit/mfa)**: + * Changed errors for endpoint `POST /auth/factors/{id}/challenge` + ## [10.3.0](https://github.com/workos/workos-python/compare/v10.2.0...v10.3.0) (2026-09-01) * [#719](https://github.com/workos/workos-python/pull/719) feat(generated)!: regenerate from spec (6 changes) diff --git a/pyproject.toml b/pyproject.toml index 6eb4608b5..27e746c36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "workos" -version = "10.3.0" +version = "10.4.0" description = "WorkOS Python Client" readme = "README.md" license = "MIT" diff --git a/uv.lock b/uv.lock index 3ca7bf04c..a3983c154 100644 --- a/uv.lock +++ b/uv.lock @@ -1363,7 +1363,7 @@ wheels = [ [[package]] name = "workos" -version = "10.3.0" +version = "10.4.0" source = { editable = "." } dependencies = [ { name = "cryptography" },