diff --git a/.changelog-pending/2026-07-02T14-59-02-e350eb0b1521b954c25625b4858b2fd14a506cd2.md b/.changelog-pending/2026-07-02T14-59-02-e350eb0b1521b954c25625b4858b2fd14a506cd2.md new file mode 100644 index 00000000..660337bb --- /dev/null +++ b/.changelog-pending/2026-07-02T14-59-02-e350eb0b1521b954c25625b4858b2fd14a506cd2.md @@ -0,0 +1,27 @@ +* [#685](https://github.com/workos/workos-python/pull/685) feat(generated): regenerate from spec (1 change) + + **Features** + * **[pipes](https://workos.com/docs/reference/pipes)**: + * Added model `DataIntegrationCredentialsDto` + * Added model `CustomProviderDefinition` + * Added model `CreateDataIntegration` + * Added model `UpdateCustomProviderDefinition` + * Added model `UpdateDataIntegration` + * Added model `DataIntegration` + * Added model `DataIntegrationList` + * Added model `DataIntegrationListListMetadata` + * Added model `DataIntegrationCredential` + * Added model `DataIntegrationCustomProvider` + * Added enum `DataIntegrationCredentialsType` + * Added enum `CustomProviderDefinitionAuthenticateVia` + * Added enum `UpdateCustomProviderDefinitionAuthenticateVia` + * Added enum `DataIntegrationState` + * Added enum `DataIntegrationCredentialType` + * Added enum `DataIntegrationCustomProviderAuthenticateVia` + * Added endpoint `GET /data-integrations` + * Added endpoint `POST /data-integrations` + * Added endpoint `GET /data-integrations/{slug}` + * Added endpoint `PUT /data-integrations/{slug}` + * Added endpoint `DELETE /data-integrations/{slug}` + * Added endpoint `POST /user_management/users/{user_id}/connected_accounts/{slug}` + * Added endpoint `PUT /user_management/users/{user_id}/connected_accounts/{slug}` diff --git a/.last-synced-sha b/.last-synced-sha index cdc071b3..639e2e87 100644 --- a/.last-synced-sha +++ b/.last-synced-sha @@ -1 +1 @@ -dc04d30b352063b5c97e45de03be5c83629f5412 +4b4e0618779460dbebc1cf5e0f02197c21796d1f diff --git a/.oagen-manifest.json b/.oagen-manifest.json index eecf2d36..86a5bc94 100644 --- a/.oagen-manifest.json +++ b/.oagen-manifest.json @@ -1,7 +1,7 @@ { "version": 2, "language": "python", - "generatedAt": "2026-07-01T18:19:12.330Z", + "generatedAt": "2026-07-02T14:58:45.916Z", "files": [ "src/workos/_client.py", "src/workos/admin_portal/__init__.py", @@ -201,9 +201,14 @@ "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_webhook_endpoint_events.py", + "src/workos/common/models/custom_provider_definition_authenticate_via.py", "src/workos/common/models/data_integration_access_token_response_error.py", + "src/workos/common/models/data_integration_credential_type.py", "src/workos/common/models/data_integration_credentials_credentials_type.py", "src/workos/common/models/data_integration_credentials_response_error.py", + "src/workos/common/models/data_integration_credentials_type.py", + "src/workos/common/models/data_integration_custom_provider_authenticate_via.py", + "src/workos/common/models/data_integration_state.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", @@ -415,6 +420,7 @@ "src/workos/common/models/session_revoked_data_impersonator.py", "src/workos/common/models/session_revoked_data_status.py", "src/workos/common/models/slim_role.py", + "src/workos/common/models/update_custom_provider_definition_authenticate_via.py", "src/workos/common/models/update_user_password_hash_type.py", "src/workos/common/models/update_webhook_endpoint_events.py", "src/workos/common/models/update_webhook_endpoint_status.py", @@ -548,11 +554,18 @@ "src/workos/pipes/_resource.py", "src/workos/pipes/models/__init__.py", "src/workos/pipes/models/connected_account.py", + "src/workos/pipes/models/connected_account_dto.py", + "src/workos/pipes/models/create_data_integration.py", + "src/workos/pipes/models/custom_provider_definition.py", + "src/workos/pipes/models/data_integration.py", "src/workos/pipes/models/data_integration_access_token_response.py", "src/workos/pipes/models/data_integration_access_token_response_access_token.py", "src/workos/pipes/models/data_integration_authorize_url_response.py", + "src/workos/pipes/models/data_integration_credential.py", + "src/workos/pipes/models/data_integration_credentials_dto.py", "src/workos/pipes/models/data_integration_credentials_response.py", "src/workos/pipes/models/data_integration_credentials_response_credential.py", + "src/workos/pipes/models/data_integration_custom_provider.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", @@ -560,6 +573,8 @@ "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_vend_credentials_request.py", + "src/workos/pipes/models/update_custom_provider_definition.py", + "src/workos/pipes/models/update_data_integration.py", "src/workos/pipes_provider/__init__.py", "src/workos/pipes_provider/_resource.py", "src/workos/pipes_provider/models/__init__.py", @@ -814,6 +829,7 @@ "tests/fixtures/connect_application_oauth.json", "tests/fixtures/connect_application_oauth_redirect_uris.json", "tests/fixtures/connected_account.json", + "tests/fixtures/connected_account_dto.json", "tests/fixtures/connection.json", "tests/fixtures/connection_activated.json", "tests/fixtures/connection_activated_data.json", @@ -837,6 +853,7 @@ "tests/fixtures/create_authorization_permission.json", "tests/fixtures/create_authorization_resource.json", "tests/fixtures/create_cors_origin.json", + "tests/fixtures/create_data_integration.json", "tests/fixtures/create_data_key_request.json", "tests/fixtures/create_data_key_response.json", "tests/fixtures/create_group.json", @@ -858,14 +875,19 @@ "tests/fixtures/create_user_invite_options.json", "tests/fixtures/create_user_organization_membership.json", "tests/fixtures/create_webhook_endpoint.json", + "tests/fixtures/custom_provider_definition.json", + "tests/fixtures/data_integration.json", "tests/fixtures/data_integration_access_token_response.json", "tests/fixtures/data_integration_access_token_response_access_token.json", "tests/fixtures/data_integration_authorize_url_response.json", "tests/fixtures/data_integration_configuration_list_response.json", "tests/fixtures/data_integration_configuration_response.json", + "tests/fixtures/data_integration_credential.json", "tests/fixtures/data_integration_credentials.json", + "tests/fixtures/data_integration_credentials_dto.json", "tests/fixtures/data_integration_credentials_response.json", "tests/fixtures/data_integration_credentials_response_credential.json", + "tests/fixtures/data_integration_custom_provider.json", "tests/fixtures/data_integrations_get_data_integration_authorize_url_request.json", "tests/fixtures/data_integrations_get_user_token_request.json", "tests/fixtures/data_integrations_list_response.json", @@ -995,6 +1017,7 @@ "tests/fixtures/list_connect_application.json", "tests/fixtures/list_connection.json", "tests/fixtures/list_cors_origin_response.json", + "tests/fixtures/list_data_integration.json", "tests/fixtures/list_directory.json", "tests/fixtures/list_directory_group.json", "tests/fixtures/list_directory_user_with_groups.json", @@ -1132,6 +1155,8 @@ "tests/fixtures/update_audit_logs_retention.json", "tests/fixtures/update_authorization_permission.json", "tests/fixtures/update_authorization_resource.json", + "tests/fixtures/update_custom_provider_definition.json", + "tests/fixtures/update_data_integration.json", "tests/fixtures/update_group.json", "tests/fixtures/update_jwt_template.json", "tests/fixtures/update_oauth_application.json", @@ -1224,6 +1249,7 @@ "tests/test_organizations.py", "tests/test_organizations_models_round_trip.py", "tests/test_pipes.py", + "tests/test_pipes_models_round_trip.py", "tests/test_pipes_provider.py", "tests/test_radar.py", "tests/test_sso.py", @@ -2022,6 +2048,38 @@ "GET /user_management/redirect_uris": { "sdkMethod": "list_redirect_uris", "service": "user_management" + }, + "GET /data-integrations": { + "sdkMethod": "list_data_integrations", + "service": "pipes" + }, + "POST /data-integrations": { + "sdkMethod": "create_data_integration", + "service": "pipes" + }, + "GET /data-integrations/{slug}": { + "sdkMethod": "get_data_integration", + "service": "pipes" + }, + "PUT /data-integrations/{slug}": { + "sdkMethod": "update_data_integration", + "service": "pipes" + }, + "DELETE /data-integrations/{slug}": { + "sdkMethod": "delete_data_integration", + "service": "pipes" + }, + "POST /user_management/radar_challenges": { + "sdkMethod": "create_radar_challenge", + "service": "user_management" + }, + "POST /user_management/users/{user_id}/connected_accounts/{slug}": { + "sdkMethod": "create_user_connected_account", + "service": "pipes" + }, + "PUT /user_management/users/{user_id}/connected_accounts/{slug}": { + "sdkMethod": "update_user_connected_account", + "service": "pipes" } } } diff --git a/src/workos/common/__init__.py b/src/workos/common/__init__.py index d8aed159..3a056e70 100644 --- a/src/workos/common/__init__.py +++ b/src/workos/common/__init__.py @@ -170,6 +170,9 @@ from .models import CreateUserInviteOptionsLocale as CreateUserInviteOptionsLocale from .models import CreateUserPasswordHashType as CreateUserPasswordHashType from .models import CreateWebhookEndpointEvents as CreateWebhookEndpointEvents +from .models import ( + CustomProviderDefinitionAuthenticateVia as CustomProviderDefinitionAuthenticateVia, +) from .models import ( DataIntegrationAccessTokenResponseError as DataIntegrationAccessTokenResponseError, ) @@ -179,6 +182,11 @@ from .models import ( DataIntegrationCredentialsResponseError as DataIntegrationCredentialsResponseError, ) +from .models import DataIntegrationCredentialsType as DataIntegrationCredentialsType +from .models import DataIntegrationCredentialType as DataIntegrationCredentialType +from .models import ( + DataIntegrationCustomProviderAuthenticateVia as DataIntegrationCustomProviderAuthenticateVia, +) from .models import ( DataIntegrationsListResponseDataAuthMethods as DataIntegrationsListResponseDataAuthMethods, ) @@ -191,6 +199,7 @@ from .models import ( DataIntegrationsListResponseDataOwnership as DataIntegrationsListResponseDataOwnership, ) +from .models import DataIntegrationState as DataIntegrationState from .models import DirectoryGroup as DirectoryGroup from .models import DirectoryState as DirectoryState from .models import DirectoryType as DirectoryType @@ -402,6 +411,9 @@ from .models import SessionRevokedData as SessionRevokedData from .models import SessionRevokedDataImpersonator as SessionRevokedDataImpersonator from .models import SlimRole as SlimRole +from .models import ( + UpdateCustomProviderDefinitionAuthenticateVia as UpdateCustomProviderDefinitionAuthenticateVia, +) from .models import UpdateUserPasswordHashType as UpdateUserPasswordHashType from .models import UpdateWebhookEndpointEvents as UpdateWebhookEndpointEvents from .models import UpdateWebhookEndpointStatus as UpdateWebhookEndpointStatus diff --git a/src/workos/common/models/__init__.py b/src/workos/common/models/__init__.py index 4c175fa7..3d76b85e 100644 --- a/src/workos/common/models/__init__.py +++ b/src/workos/common/models/__init__.py @@ -288,6 +288,9 @@ from .create_webhook_endpoint_events import ( CreateWebhookEndpointEvents as CreateWebhookEndpointEvents, ) +from .custom_provider_definition_authenticate_via import ( + CustomProviderDefinitionAuthenticateVia as CustomProviderDefinitionAuthenticateVia, +) from .data_integration_access_token_response_error import ( DataIntegrationAccessTokenResponseError as DataIntegrationAccessTokenResponseError, ) @@ -297,6 +300,15 @@ from .data_integration_credentials_response_error import ( DataIntegrationCredentialsResponseError as DataIntegrationCredentialsResponseError, ) +from .data_integration_credentials_type import ( + DataIntegrationCredentialsType as DataIntegrationCredentialsType, +) +from .data_integration_credential_type import ( + DataIntegrationCredentialType as DataIntegrationCredentialType, +) +from .data_integration_custom_provider_authenticate_via import ( + DataIntegrationCustomProviderAuthenticateVia as DataIntegrationCustomProviderAuthenticateVia, +) from .data_integrations_list_response_data_auth_methods import ( DataIntegrationsListResponseDataAuthMethods as DataIntegrationsListResponseDataAuthMethods, ) @@ -309,6 +321,7 @@ from .data_integrations_list_response_data_ownership import ( DataIntegrationsListResponseDataOwnership as DataIntegrationsListResponseDataOwnership, ) +from .data_integration_state import DataIntegrationState as DataIntegrationState from .directory_user_state import * # noqa: F401,F403 from .directory_group import DirectoryGroup as DirectoryGroup from .directory_state import DirectoryState as DirectoryState @@ -656,6 +669,9 @@ SessionRevokedDataImpersonator as SessionRevokedDataImpersonator, ) from .slim_role import SlimRole as SlimRole +from .update_custom_provider_definition_authenticate_via import ( + UpdateCustomProviderDefinitionAuthenticateVia as UpdateCustomProviderDefinitionAuthenticateVia, +) from .update_user_password_hash_type import ( UpdateUserPasswordHashType as UpdateUserPasswordHashType, ) diff --git a/src/workos/common/models/connected_account_state.py b/src/workos/common/models/connected_account_state.py index 743b7d0b..e36328ef 100644 --- a/src/workos/common/models/connected_account_state.py +++ b/src/workos/common/models/connected_account_state.py @@ -14,7 +14,6 @@ class ConnectedAccountState(str, Enum): CONNECTED = "connected" NEEDS_REAUTHORIZATION = "needs_reauthorization" - DISCONNECTED = "disconnected" @classmethod def _missing_(cls, value: object) -> Optional["ConnectedAccountState"]: @@ -26,6 +25,4 @@ def _missing_(cls, value: object) -> Optional["ConnectedAccountState"]: return unknown -ConnectedAccountStateLiteral: TypeAlias = Literal[ - "connected", "needs_reauthorization", "disconnected" -] +ConnectedAccountStateLiteral: TypeAlias = Literal["connected", "needs_reauthorization"] diff --git a/src/workos/common/models/custom_provider_definition_authenticate_via.py b/src/workos/common/models/custom_provider_definition_authenticate_via.py new file mode 100644 index 00000000..593d57db --- /dev/null +++ b/src/workos/common/models/custom_provider_definition_authenticate_via.py @@ -0,0 +1,32 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of custom provider definition authenticate via values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Optional +from typing import Literal, TypeAlias + + +class CustomProviderDefinitionAuthenticateVia(str, Enum): + """Known values for CustomProviderDefinitionAuthenticateVia.""" + + REQUEST_BODY = "request_body" + BASIC_AUTH_HEADER = "basic_auth_header" + + @classmethod + def _missing_( + cls, value: object + ) -> Optional["CustomProviderDefinitionAuthenticateVia"]: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +CustomProviderDefinitionAuthenticateViaLiteral: TypeAlias = Literal[ + "request_body", "basic_auth_header" +] diff --git a/src/workos/common/models/data_integration_credential_type.py b/src/workos/common/models/data_integration_credential_type.py new file mode 100644 index 00000000..30d4834c --- /dev/null +++ b/src/workos/common/models/data_integration_credential_type.py @@ -0,0 +1,28 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of data integration credential type values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Optional +from typing import Literal, TypeAlias + + +class DataIntegrationCredentialType(str, Enum): + """Known values for DataIntegrationCredentialType.""" + + CUSTOM = "custom" + ORGANIZATION = "organization" + + @classmethod + def _missing_(cls, value: object) -> Optional["DataIntegrationCredentialType"]: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +DataIntegrationCredentialTypeLiteral: TypeAlias = Literal["custom", "organization"] diff --git a/src/workos/common/models/data_integration_credentials_type.py b/src/workos/common/models/data_integration_credentials_type.py new file mode 100644 index 00000000..d5ac6cf1 --- /dev/null +++ b/src/workos/common/models/data_integration_credentials_type.py @@ -0,0 +1,7 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias +from .data_integration_credential_type import DataIntegrationCredentialType + +DataIntegrationCredentialsType: TypeAlias = DataIntegrationCredentialType +__all__ = ["DataIntegrationCredentialsType"] diff --git a/src/workos/common/models/data_integration_custom_provider_authenticate_via.py b/src/workos/common/models/data_integration_custom_provider_authenticate_via.py new file mode 100644 index 00000000..bb8289a2 --- /dev/null +++ b/src/workos/common/models/data_integration_custom_provider_authenticate_via.py @@ -0,0 +1,11 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias +from .custom_provider_definition_authenticate_via import ( + CustomProviderDefinitionAuthenticateVia, +) + +DataIntegrationCustomProviderAuthenticateVia: TypeAlias = ( + CustomProviderDefinitionAuthenticateVia +) +__all__ = ["DataIntegrationCustomProviderAuthenticateVia"] diff --git a/src/workos/common/models/data_integration_state.py b/src/workos/common/models/data_integration_state.py new file mode 100644 index 00000000..ffdd7bf4 --- /dev/null +++ b/src/workos/common/models/data_integration_state.py @@ -0,0 +1,29 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Enumeration of data integration state values.""" + +from __future__ import annotations + +from enum import Enum +from typing import Optional +from typing import Literal, TypeAlias + + +class DataIntegrationState(str, Enum): + """Known values for DataIntegrationState.""" + + VALID = "valid" + INVALID = "invalid" + REQUESTED = "requested" + + @classmethod + def _missing_(cls, value: object) -> Optional["DataIntegrationState"]: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +DataIntegrationStateLiteral: TypeAlias = Literal["valid", "invalid", "requested"] diff --git a/src/workos/common/models/data_integrations_list_response_data_connected_account_state.py b/src/workos/common/models/data_integrations_list_response_data_connected_account_state.py index 718e5709..e10aa638 100644 --- a/src/workos/common/models/data_integrations_list_response_data_connected_account_state.py +++ b/src/workos/common/models/data_integrations_list_response_data_connected_account_state.py @@ -1,7 +1,33 @@ # This file is auto-generated by oagen. Do not edit. -from typing import TypeAlias -from .connected_account_state import ConnectedAccountState +"""Enumeration of data integrations list response data connected account state values.""" -DataIntegrationsListResponseDataConnectedAccountState: TypeAlias = ConnectedAccountState -__all__ = ["DataIntegrationsListResponseDataConnectedAccountState"] +from __future__ import annotations + +from enum import Enum +from typing import Optional +from typing import Literal, TypeAlias + + +class DataIntegrationsListResponseDataConnectedAccountState(str, Enum): + """Known values for DataIntegrationsListResponseDataConnectedAccountState.""" + + CONNECTED = "connected" + NEEDS_REAUTHORIZATION = "needs_reauthorization" + DISCONNECTED = "disconnected" + + @classmethod + def _missing_( + cls, value: object + ) -> Optional["DataIntegrationsListResponseDataConnectedAccountState"]: + if not isinstance(value, str): + return None + unknown = str.__new__(cls, value) + unknown._name_ = value.upper() + unknown._value_ = value + return unknown + + +DataIntegrationsListResponseDataConnectedAccountStateLiteral: TypeAlias = Literal[ + "connected", "needs_reauthorization", "disconnected" +] diff --git a/src/workos/common/models/update_custom_provider_definition_authenticate_via.py b/src/workos/common/models/update_custom_provider_definition_authenticate_via.py new file mode 100644 index 00000000..25f88fc8 --- /dev/null +++ b/src/workos/common/models/update_custom_provider_definition_authenticate_via.py @@ -0,0 +1,11 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias +from .custom_provider_definition_authenticate_via import ( + CustomProviderDefinitionAuthenticateVia, +) + +UpdateCustomProviderDefinitionAuthenticateVia: TypeAlias = ( + CustomProviderDefinitionAuthenticateVia +) +__all__ = ["UpdateCustomProviderDefinitionAuthenticateVia"] diff --git a/src/workos/pipes/_resource.py b/src/workos/pipes/_resource.py index 0883c2d1..98812210 100644 --- a/src/workos/pipes/_resource.py +++ b/src/workos/pipes/_resource.py @@ -2,19 +2,26 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient -from .._types import RequestOptions +from .._types import RequestOptions, enum_value from .models import ( + CustomProviderDefinition, + DataIntegration, DataIntegrationAccessTokenResponse, DataIntegrationAuthorizeUrlResponse, + DataIntegrationCredentialsDto, DataIntegrationCredentialsResponse, DataIntegrationsListResponse, + UpdateCustomProviderDefinition, ) from workos.common.models.connected_account import ConnectedAccount +from workos.common.models.connected_account_state import ConnectedAccountState +from workos.common.models.pagination_order import PaginationOrder +from .._pagination import AsyncPage, SyncPage class Pipes: @@ -23,6 +30,225 @@ class Pipes: def __init__(self, client: "WorkOSClient") -> None: self._client = client + def list_data_integrations( + self, + *, + limit: Optional[int] = None, + before: Optional[str] = None, + after: Optional[str] = None, + order: Optional[Union[PaginationOrder, str]] = "desc", + request_options: Optional[RequestOptions] = None, + ) -> SyncPage[DataIntegration]: + """List data integrations + + Lists the environment's data integrations configured with `custom` or `organization` credentials, including custom providers. + + 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[DataIntegration] + + 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=("data-integrations",), + model=DataIntegration, + params=params, + request_options=request_options, + ) + + def create_data_integration( + self, + *, + provider: str, + description: Optional[str] = None, + enabled: Optional[bool] = None, + scopes: Optional[List[str]] = None, + credentials: Optional[DataIntegrationCredentialsDto] = None, + custom_provider: Optional[CustomProviderDefinition] = None, + request_options: Optional[RequestOptions] = None, + ) -> 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. For a built-in provider, pass its slug as `provider`. For a custom provider, pass a new slug plus a `custom_provider` definition. + + 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. + 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. + credentials: The credentials to configure for the Data Integration. Required for both built-in and custom providers. + 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. + + Returns: + DataIntegration + + Raises: + BadRequestError: If the request is malformed (400). + 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. + """ + body: Dict[str, Any] = { + k: v + for k, v in { + "provider": provider, + "description": description, + "enabled": enabled, + "scopes": scopes, + "credentials": credentials.to_dict() + if credentials 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 + } + return self._client.request( + method="post", + path=("data-integrations",), + body=body, + model=DataIntegration, + request_options=request_options, + ) + + def get_data_integration( + self, + slug: str, + *, + request_options: Optional[RequestOptions] = None, + ) -> DataIntegration: + """Get a data integration + + Retrieves a data integration by its slug. + + 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)), + model=DataIntegration, + request_options=request_options, + ) + + def update_data_integration( + self, + slug: str, + *, + description: Optional[str] = None, + enabled: Optional[bool] = None, + scopes: Optional[List[str]] = None, + credentials: Optional[DataIntegrationCredentialsDto] = None, + custom_provider: Optional[UpdateCustomProviderDefinition] = None, + request_options: Optional[RequestOptions] = None, + ) -> 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. + + 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 credentials for the Data Integration. When provided, rotates the stored client secret. + 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). + 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. + """ + body: Dict[str, Any] = { + k: v + for k, v in { + "description": description, + "enabled": enabled, + "scopes": scopes, + "credentials": credentials.to_dict() + if credentials 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 + } + return self._client.request( + method="put", + path=("data-integrations", str(slug)), + body=body, + model=DataIntegration, + request_options=request_options, + ) + + def delete_data_integration( + self, + slug: str, + *, + request_options: Optional[RequestOptions] = None, + ) -> 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. + + 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)), + request_options=request_options, + ) + def update_data_integration_api_key( self, slug: str, @@ -257,6 +483,148 @@ def get_user_connected_account( request_options=request_options, ) + def create_user_connected_account( + self, + user_id: str, + slug: str, + *, + access_token: Optional[str] = None, + refresh_token: Optional[str] = None, + expires_at: Optional[str] = None, + scopes: Optional[List[str]] = None, + state: Optional[Union[ConnectedAccountState, str]] = None, + organization_id: Optional[str] = None, + request_options: Optional[RequestOptions] = None, + ) -> ConnectedAccount: + """Import a connected account + + Imports a [connected account](https://workos.com/docs/reference/pipes/connected-account) for a user by providing OAuth tokens directly. Use this to migrate existing connections or set up connections without going through the OAuth flow. + + 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`). + 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. + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + 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 + } + params: Dict[str, Any] = { + k: v + for k, v in { + "organization_id": organization_id, + }.items() + if v is not None + } + return self._client.request( + method="post", + path=( + "user_management", + "users", + str(user_id), + "connected_accounts", + str(slug), + ), + body=body, + params=params, + model=ConnectedAccount, + request_options=request_options, + ) + + def update_user_connected_account( + self, + user_id: str, + slug: str, + *, + access_token: Optional[str] = None, + refresh_token: Optional[str] = None, + expires_at: Optional[str] = None, + scopes: Optional[List[str]] = None, + state: Optional[Union[ConnectedAccountState, str]] = None, + organization_id: Optional[str] = None, + request_options: Optional[RequestOptions] = None, + ) -> ConnectedAccount: + """Update a connected account + + Updates a user's [connected account](https://workos.com/docs/reference/pipes/connected-account) tokens, scopes, or state for a specific provider. + + 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`). + 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. + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + 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). + 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 { + "organization_id": organization_id, + }.items() + if v is not None + } + return self._client.request( + method="put", + path=( + "user_management", + "users", + str(user_id), + "connected_accounts", + str(slug), + ), + body=body, + params=params, + model=ConnectedAccount, + request_options=request_options, + ) + def delete_user_connected_account( self, user_id: str, @@ -348,6 +716,225 @@ class AsyncPipes: def __init__(self, client: "AsyncWorkOSClient") -> None: self._client = client + async def list_data_integrations( + self, + *, + limit: Optional[int] = None, + before: Optional[str] = None, + after: Optional[str] = None, + order: Optional[Union[PaginationOrder, str]] = "desc", + request_options: Optional[RequestOptions] = None, + ) -> AsyncPage[DataIntegration]: + """List data integrations + + Lists the environment's data integrations configured with `custom` or `organization` credentials, including custom providers. + + 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[DataIntegration] + + 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=("data-integrations",), + model=DataIntegration, + params=params, + request_options=request_options, + ) + + async def create_data_integration( + self, + *, + provider: str, + description: Optional[str] = None, + enabled: Optional[bool] = None, + scopes: Optional[List[str]] = None, + credentials: Optional[DataIntegrationCredentialsDto] = None, + custom_provider: Optional[CustomProviderDefinition] = None, + request_options: Optional[RequestOptions] = None, + ) -> 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. For a built-in provider, pass its slug as `provider`. For a custom provider, pass a new slug plus a `custom_provider` definition. + + 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. + 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. + credentials: The credentials to configure for the Data Integration. Required for both built-in and custom providers. + 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. + + Returns: + DataIntegration + + Raises: + BadRequestError: If the request is malformed (400). + 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. + """ + body: Dict[str, Any] = { + k: v + for k, v in { + "provider": provider, + "description": description, + "enabled": enabled, + "scopes": scopes, + "credentials": credentials.to_dict() + if credentials 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 + } + return await self._client.request( + method="post", + path=("data-integrations",), + body=body, + model=DataIntegration, + request_options=request_options, + ) + + async def get_data_integration( + self, + slug: str, + *, + request_options: Optional[RequestOptions] = None, + ) -> DataIntegration: + """Get a data integration + + Retrieves a data integration by its slug. + + 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)), + model=DataIntegration, + request_options=request_options, + ) + + async def update_data_integration( + self, + slug: str, + *, + description: Optional[str] = None, + enabled: Optional[bool] = None, + scopes: Optional[List[str]] = None, + credentials: Optional[DataIntegrationCredentialsDto] = None, + custom_provider: Optional[UpdateCustomProviderDefinition] = None, + request_options: Optional[RequestOptions] = None, + ) -> 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. + + 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 credentials for the Data Integration. When provided, rotates the stored client secret. + 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). + 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. + """ + body: Dict[str, Any] = { + k: v + for k, v in { + "description": description, + "enabled": enabled, + "scopes": scopes, + "credentials": credentials.to_dict() + if credentials 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 + } + return await self._client.request( + method="put", + path=("data-integrations", str(slug)), + body=body, + model=DataIntegration, + request_options=request_options, + ) + + async def delete_data_integration( + self, + slug: str, + *, + request_options: Optional[RequestOptions] = None, + ) -> 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. + + 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)), + request_options=request_options, + ) + async def update_data_integration_api_key( self, slug: str, @@ -582,6 +1169,148 @@ async def get_user_connected_account( request_options=request_options, ) + async def create_user_connected_account( + self, + user_id: str, + slug: str, + *, + access_token: Optional[str] = None, + refresh_token: Optional[str] = None, + expires_at: Optional[str] = None, + scopes: Optional[List[str]] = None, + state: Optional[Union[ConnectedAccountState, str]] = None, + organization_id: Optional[str] = None, + request_options: Optional[RequestOptions] = None, + ) -> ConnectedAccount: + """Import a connected account + + Imports a [connected account](https://workos.com/docs/reference/pipes/connected-account) for a user by providing OAuth tokens directly. Use this to migrate existing connections or set up connections without going through the OAuth flow. + + 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`). + 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. + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + 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 + } + params: Dict[str, Any] = { + k: v + for k, v in { + "organization_id": organization_id, + }.items() + if v is not None + } + return await self._client.request( + method="post", + path=( + "user_management", + "users", + str(user_id), + "connected_accounts", + str(slug), + ), + body=body, + params=params, + model=ConnectedAccount, + request_options=request_options, + ) + + async def update_user_connected_account( + self, + user_id: str, + slug: str, + *, + access_token: Optional[str] = None, + refresh_token: Optional[str] = None, + expires_at: Optional[str] = None, + scopes: Optional[List[str]] = None, + state: Optional[Union[ConnectedAccountState, str]] = None, + organization_id: Optional[str] = None, + request_options: Optional[RequestOptions] = None, + ) -> ConnectedAccount: + """Update a connected account + + Updates a user's [connected account](https://workos.com/docs/reference/pipes/connected-account) tokens, scopes, or state for a specific provider. + + 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`). + 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. + organization_id: An [Organization](https://workos.com/docs/reference/organization) identifier. Optional parameter if the connection is scoped to an organization. + 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). + 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 { + "organization_id": organization_id, + }.items() + if v is not None + } + return await self._client.request( + method="put", + path=( + "user_management", + "users", + str(user_id), + "connected_accounts", + str(slug), + ), + body=body, + params=params, + model=ConnectedAccount, + request_options=request_options, + ) + async def delete_user_connected_account( self, user_id: str, diff --git a/src/workos/pipes/models/__init__.py b/src/workos/pipes/models/__init__.py index 0959db7a..0785e18c 100644 --- a/src/workos/pipes/models/__init__.py +++ b/src/workos/pipes/models/__init__.py @@ -2,6 +2,12 @@ from .connected_account import * # noqa: F401,F403 from workos.common.models.connected_account import ConnectedAccount as ConnectedAccount +from .connected_account_dto import ConnectedAccountDto as ConnectedAccountDto +from .create_data_integration import CreateDataIntegration as CreateDataIntegration +from .custom_provider_definition import ( + CustomProviderDefinition as CustomProviderDefinition, +) +from .data_integration import DataIntegration as DataIntegration from .data_integration_access_token_response import ( DataIntegrationAccessTokenResponse as DataIntegrationAccessTokenResponse, ) @@ -11,12 +17,21 @@ from .data_integration_authorize_url_response import ( DataIntegrationAuthorizeUrlResponse as DataIntegrationAuthorizeUrlResponse, ) +from .data_integration_credential import ( + DataIntegrationCredential as DataIntegrationCredential, +) +from .data_integration_credentials_dto import ( + DataIntegrationCredentialsDto as DataIntegrationCredentialsDto, +) from .data_integration_credentials_response import ( DataIntegrationCredentialsResponse as DataIntegrationCredentialsResponse, ) from .data_integration_credentials_response_credential import ( DataIntegrationCredentialsResponseCredential as DataIntegrationCredentialsResponseCredential, ) +from .data_integration_custom_provider import ( + DataIntegrationCustomProvider as DataIntegrationCustomProvider, +) from .data_integrations_get_data_integration_authorize_url_request import ( DataIntegrationsGetDataIntegrationAuthorizeUrlRequest as DataIntegrationsGetDataIntegrationAuthorizeUrlRequest, ) @@ -38,3 +53,7 @@ from .data_integrations_vend_credentials_request import ( DataIntegrationsVendCredentialsRequest as DataIntegrationsVendCredentialsRequest, ) +from .update_custom_provider_definition import ( + UpdateCustomProviderDefinition as UpdateCustomProviderDefinition, +) +from .update_data_integration import UpdateDataIntegration as UpdateDataIntegration diff --git a/src/workos/pipes/models/connected_account_dto.py b/src/workos/pipes/models/connected_account_dto.py new file mode 100644 index 00000000..c2eee050 --- /dev/null +++ b/src/workos/pipes/models/connected_account_dto.py @@ -0,0 +1,62 @@ +# 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, Dict, List, Optional +from workos._types import _raise_deserialize_error +from workos._types import _format_datetime, _parse_datetime +from workos.common.models.connected_account_state import ConnectedAccountState + + +@dataclass(slots=True) +class ConnectedAccountDto: + """Connected Account Dto model.""" + + access_token: Optional[str] = None + """The OAuth access token for the connected account.""" + refresh_token: Optional[str] = None + """The OAuth refresh token for the connected account.""" + expires_at: Optional[datetime] = None + """The ISO-8601 timestamp when the access token expires. Required when `access_token` is provided for tokens that expire.""" + scopes: Optional[List[str]] = None + """The OAuth scopes granted for this connection.""" + state: Optional["ConnectedAccountState"] = 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]) -> "ConnectedAccountDto": + """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=ConnectedAccountState(_v_state) + if (_v_state := data.get("state")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("ConnectedAccountDto", 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/create_data_integration.py b/src/workos/pipes/models/create_data_integration.py new file mode 100644 index 00000000..2a4e0d00 --- /dev/null +++ b/src/workos/pipes/models/create_data_integration.py @@ -0,0 +1,72 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import cast +from typing import Any, Dict, List, Optional +from workos._types import _raise_deserialize_error + +from .custom_provider_definition import CustomProviderDefinition +from .data_integration_credentials_dto import DataIntegrationCredentialsDto + + +@dataclass(slots=True) +class CreateDataIntegration: + """Create Data Integration model.""" + + 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.""" + description: Optional[str] = None + """An optional description of the Data Integration.""" + enabled: Optional[bool] = None + """Whether the Data Integration is enabled. Defaults to `false`.""" + scopes: Optional[List[str]] = None + """The OAuth scopes to request for the Data Integration. Defaults to the provider's configured scopes when omitted.""" + credentials: Optional["DataIntegrationCredentialsDto"] = None + """The credentials to configure for the Data Integration. Required for both built-in and custom providers.""" + custom_provider: Optional["CustomProviderDefinition"] = None + """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.""" + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "CreateDataIntegration": + """Deserialize from a dictionary.""" + try: + return cls( + provider=data["provider"], + description=data.get("description"), + enabled=data.get("enabled"), + scopes=data.get("scopes"), + credentials=DataIntegrationCredentialsDto.from_dict( + cast(Dict[str, Any], _v_credentials) + ) + if (_v_credentials := data.get("credentials")) is not None + else None, + custom_provider=CustomProviderDefinition.from_dict( + cast(Dict[str, Any], _v_custom_provider) + ) + if (_v_custom_provider := data.get("custom_provider")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateDataIntegration", e) + + def to_dict(self) -> Dict[str, Any]: + """Serialize to a dictionary.""" + result: Dict[str, Any] = {} + result["provider"] = self.provider + if self.description is not None: + result["description"] = self.description + else: + result["description"] = None + if self.enabled is not None: + result["enabled"] = self.enabled + if self.scopes is not None: + result["scopes"] = self.scopes + else: + result["scopes"] = None + if self.credentials is not None: + result["credentials"] = self.credentials.to_dict() + if self.custom_provider is not None: + result["custom_provider"] = self.custom_provider.to_dict() + return result diff --git a/src/workos/pipes/models/custom_provider_definition.py b/src/workos/pipes/models/custom_provider_definition.py new file mode 100644 index 00000000..494a71ce --- /dev/null +++ b/src/workos/pipes/models/custom_provider_definition.py @@ -0,0 +1,97 @@ +# 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, Dict, Optional +from workos._types import _raise_deserialize_error +from workos.common.models.custom_provider_definition_authenticate_via import ( + CustomProviderDefinitionAuthenticateVia, +) + + +@dataclass(slots=True) +class CustomProviderDefinition: + """Custom Provider Definition model.""" + + name: str + """A descriptive name for the custom provider.""" + authorization_url: str + """The provider's OAuth authorization endpoint.""" + token_url: str + """The provider's OAuth token endpoint.""" + refresh_token_url: Optional[str] = None + """The endpoint used to refresh tokens, if different from the token endpoint.""" + pkce_enabled: Optional[bool] = None + """Whether PKCE is used during the authorization code flow. Defaults to `true`.""" + request_scope_separator: Optional[str] = None + """The separator used to join requested scopes. Defaults to a space.""" + scopes_required: Optional[bool] = None + """Whether at least one scope must be selected when connecting an account. Defaults to `false`.""" + client_secret_required: Optional[bool] = None + """Whether a client secret is required for this provider. Defaults to `true`.""" + additional_authorization_parameters: Optional[Dict[str, str]] = None + """Additional static query parameters appended to the authorization request.""" + token_body_content_type: Optional[str] = None + """The Content-Type used when exchanging the token request.""" + authenticate_via: Optional["CustomProviderDefinitionAuthenticateVia"] = None + """How client credentials are sent when exchanging authorization codes and refreshing tokens.""" + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "CustomProviderDefinition": + """Deserialize from a dictionary.""" + try: + return cls( + name=data["name"], + authorization_url=data["authorization_url"], + token_url=data["token_url"], + refresh_token_url=data.get("refresh_token_url"), + pkce_enabled=data.get("pkce_enabled"), + request_scope_separator=data.get("request_scope_separator"), + scopes_required=data.get("scopes_required"), + client_secret_required=data.get("client_secret_required"), + additional_authorization_parameters=data.get( + "additional_authorization_parameters" + ), + token_body_content_type=data.get("token_body_content_type"), + authenticate_via=CustomProviderDefinitionAuthenticateVia( + _v_authenticate_via + ) + if (_v_authenticate_via := data.get("authenticate_via")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CustomProviderDefinition", e) + + def to_dict(self) -> Dict[str, Any]: + """Serialize to a dictionary.""" + result: Dict[str, Any] = {} + result["name"] = self.name + result["authorization_url"] = self.authorization_url + result["token_url"] = self.token_url + if self.refresh_token_url is not None: + result["refresh_token_url"] = self.refresh_token_url + else: + result["refresh_token_url"] = None + if self.pkce_enabled is not None: + result["pkce_enabled"] = self.pkce_enabled + if self.request_scope_separator is not None: + result["request_scope_separator"] = self.request_scope_separator + if self.scopes_required is not None: + result["scopes_required"] = self.scopes_required + if self.client_secret_required is not None: + result["client_secret_required"] = self.client_secret_required + if self.additional_authorization_parameters is not None: + result["additional_authorization_parameters"] = ( + self.additional_authorization_parameters + ) + if self.token_body_content_type is not None: + result["token_body_content_type"] = self.token_body_content_type + if self.authenticate_via is not None: + result["authenticate_via"] = ( + self.authenticate_via.value + if isinstance(self.authenticate_via, Enum) + else self.authenticate_via + ) + return result diff --git a/src/workos/pipes/models/data_integration.py b/src/workos/pipes/models/data_integration.py new file mode 100644 index 00000000..3ea6db62 --- /dev/null +++ b/src/workos/pipes/models/data_integration.py @@ -0,0 +1,104 @@ +# 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 cast +from typing import Any, Dict, List, Literal, Optional +from workos._types import _raise_deserialize_error +from workos._types import _format_datetime, _parse_datetime + +from .data_integration_credential import DataIntegrationCredential +from .data_integration_custom_provider import DataIntegrationCustomProvider +from workos.common.models.data_integration_state import DataIntegrationState + + +@dataclass(slots=True) +class DataIntegration: + """Data Integration model.""" + + object: Literal["data_integration"] + """Distinguishes the Data Integration object.""" + id: str + """Unique identifier of the Data Integration.""" + slug: str + """The provider slug for this Data Integration.""" + integration_type: str + """The integration type derived from the provider.""" + description: Optional[str] + """An optional description of the Data Integration.""" + enabled: bool + """Whether the Data Integration is enabled.""" + state: "DataIntegrationState" + """The state of the Data Integration.""" + scopes: Optional[List[str]] + """The OAuth scopes configured for the Data Integration. `null` when the provider's configured scopes are used.""" + redirect_uri: str + """The OAuth redirect URI to register with the provider when configuring the custom application.""" + credentials: "DataIntegrationCredential" + """The credentials configured for the Data Integration.""" + custom_provider: Optional["DataIntegrationCustomProvider"] + """The OAuth definition when this is a custom provider; `null` for built-in providers.""" + created_at: datetime + """An ISO 8601 timestamp.""" + updated_at: datetime + """An ISO 8601 timestamp.""" + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "DataIntegration": + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "data_integration"), + id=data["id"], + slug=data["slug"], + integration_type=data["integration_type"], + description=data["description"], + enabled=data["enabled"], + state=DataIntegrationState(data["state"]), + scopes=data["scopes"], + redirect_uri=data["redirect_uri"], + credentials=DataIntegrationCredential.from_dict( + cast(Dict[str, Any], data["credentials"]) + ), + custom_provider=DataIntegrationCustomProvider.from_dict( + cast(Dict[str, Any], _v_custom_provider) + ) + if (_v_custom_provider := data["custom_provider"]) is not None + else None, + created_at=_parse_datetime(data["created_at"]), + updated_at=_parse_datetime(data["updated_at"]), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("DataIntegration", 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["slug"] = self.slug + result["integration_type"] = self.integration_type + if self.description is not None: + result["description"] = self.description + else: + result["description"] = None + result["enabled"] = self.enabled + result["state"] = ( + self.state.value if isinstance(self.state, Enum) else self.state + ) + if self.scopes is not None: + result["scopes"] = self.scopes + else: + result["scopes"] = None + result["redirect_uri"] = self.redirect_uri + result["credentials"] = self.credentials.to_dict() + if self.custom_provider is not None: + result["custom_provider"] = self.custom_provider.to_dict() + else: + result["custom_provider"] = None + result["created_at"] = _format_datetime(self.created_at) + result["updated_at"] = _format_datetime(self.updated_at) + return result diff --git a/src/workos/pipes/models/data_integration_credential.py b/src/workos/pipes/models/data_integration_credential.py new file mode 100644 index 00000000..e33e3153 --- /dev/null +++ b/src/workos/pipes/models/data_integration_credential.py @@ -0,0 +1,49 @@ +# 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, Dict, Optional +from workos._types import _raise_deserialize_error +from workos.common.models.data_integration_credential_type import ( + DataIntegrationCredentialType, +) + + +@dataclass(slots=True) +class DataIntegrationCredential: + """The credentials configured for the Data Integration.""" + + type: "DataIntegrationCredentialType" + """The credentials type. `custom` uses your own OAuth app credentials; `organization` has each organization supply its own credentials (so `client_id`/`redacted_client_secret` are null on the integration itself).""" + client_id: Optional[str] + """The OAuth client ID configured for the provider app. Null for `organization` credentials.""" + redacted_client_secret: Optional[str] + """The last four characters of the OAuth client secret. The full secret is never returned. Null for `organization` credentials.""" + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "DataIntegrationCredential": + """Deserialize from a dictionary.""" + try: + return cls( + type=DataIntegrationCredentialType(data["type"]), + client_id=data["client_id"], + redacted_client_secret=data["redacted_client_secret"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("DataIntegrationCredential", e) + + def to_dict(self) -> Dict[str, Any]: + """Serialize to a dictionary.""" + result: Dict[str, Any] = {} + result["type"] = self.type.value if isinstance(self.type, Enum) else self.type + if self.client_id is not None: + result["client_id"] = self.client_id + else: + result["client_id"] = None + if self.redacted_client_secret is not None: + result["redacted_client_secret"] = self.redacted_client_secret + else: + result["redacted_client_secret"] = None + return result diff --git a/src/workos/pipes/models/data_integration_credentials_dto.py b/src/workos/pipes/models/data_integration_credentials_dto.py new file mode 100644 index 00000000..7e6ec8d8 --- /dev/null +++ b/src/workos/pipes/models/data_integration_credentials_dto.py @@ -0,0 +1,45 @@ +# 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, Dict, Optional +from workos._types import _raise_deserialize_error +from workos.common.models.data_integration_credentials_type import ( + DataIntegrationCredentialsType, +) + + +@dataclass(slots=True) +class DataIntegrationCredentialsDto: + """Data Integration Credentials Dto model.""" + + type: "DataIntegrationCredentialsType" + """The credentials type. `custom` uses your own OAuth app credentials; `organization` has each organization supply its own credentials (configured per-organization).""" + client_id: Optional[str] = None + """OAuth client ID for the provider app. Required when `type` is `custom`; omit for `organization`.""" + client_secret: Optional[str] = None + """OAuth client secret for the provider app. Required when `type` is `custom`; omit for `organization`.""" + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "DataIntegrationCredentialsDto": + """Deserialize from a dictionary.""" + try: + return cls( + type=DataIntegrationCredentialsType(data["type"]), + client_id=data.get("client_id"), + client_secret=data.get("client_secret"), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("DataIntegrationCredentialsDto", e) + + def to_dict(self) -> Dict[str, Any]: + """Serialize to a dictionary.""" + result: Dict[str, Any] = {} + result["type"] = self.type.value if isinstance(self.type, Enum) else self.type + if self.client_id is not None: + result["client_id"] = self.client_id + if self.client_secret is not None: + result["client_secret"] = self.client_secret + return result diff --git a/src/workos/pipes/models/data_integration_custom_provider.py b/src/workos/pipes/models/data_integration_custom_provider.py new file mode 100644 index 00000000..a7557990 --- /dev/null +++ b/src/workos/pipes/models/data_integration_custom_provider.py @@ -0,0 +1,94 @@ +# 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, Dict, Optional +from workos._types import _raise_deserialize_error +from workos.common.models.data_integration_custom_provider_authenticate_via import ( + DataIntegrationCustomProviderAuthenticateVia, +) + + +@dataclass(slots=True) +class DataIntegrationCustomProvider: + """Data Integration Custom Provider model.""" + + name: str + """A descriptive name for the custom provider.""" + authorization_url: Optional[str] + """The provider's OAuth authorization endpoint.""" + token_url: Optional[str] + """The provider's OAuth token endpoint.""" + refresh_token_url: Optional[str] + """The endpoint used to refresh tokens, if different from the token endpoint.""" + pkce_enabled: bool + """Whether PKCE is used during the authorization code flow.""" + request_scope_separator: str + """The separator used to join requested scopes.""" + scopes_required: bool + """Whether at least one scope must be selected when connecting an account.""" + client_secret_required: bool + """Whether a client secret is required for this provider.""" + additional_authorization_parameters: Dict[str, str] + """Additional static query parameters appended to the authorization request.""" + token_body_content_type: str + """The Content-Type used when exchanging the token request.""" + authenticate_via: "DataIntegrationCustomProviderAuthenticateVia" + """How client credentials are sent when exchanging authorization codes and refreshing tokens.""" + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "DataIntegrationCustomProvider": + """Deserialize from a dictionary.""" + try: + return cls( + name=data["name"], + authorization_url=data["authorization_url"], + token_url=data["token_url"], + refresh_token_url=data["refresh_token_url"], + pkce_enabled=data["pkce_enabled"], + request_scope_separator=data["request_scope_separator"], + scopes_required=data["scopes_required"], + client_secret_required=data["client_secret_required"], + additional_authorization_parameters=data[ + "additional_authorization_parameters" + ], + token_body_content_type=data["token_body_content_type"], + authenticate_via=DataIntegrationCustomProviderAuthenticateVia( + data["authenticate_via"] + ), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("DataIntegrationCustomProvider", e) + + def to_dict(self) -> Dict[str, Any]: + """Serialize to a dictionary.""" + result: Dict[str, Any] = {} + result["name"] = self.name + if self.authorization_url is not None: + result["authorization_url"] = self.authorization_url + else: + result["authorization_url"] = None + if self.token_url is not None: + result["token_url"] = self.token_url + else: + result["token_url"] = None + if self.refresh_token_url is not None: + result["refresh_token_url"] = self.refresh_token_url + else: + result["refresh_token_url"] = None + result["pkce_enabled"] = self.pkce_enabled + result["request_scope_separator"] = self.request_scope_separator + result["scopes_required"] = self.scopes_required + result["client_secret_required"] = self.client_secret_required + result["additional_authorization_parameters"] = ( + self.additional_authorization_parameters + ) + result["token_body_content_type"] = self.token_body_content_type + result["authenticate_via"] = ( + self.authenticate_via.value + if isinstance(self.authenticate_via, Enum) + else self.authenticate_via + ) + return result diff --git a/src/workos/pipes/models/update_custom_provider_definition.py b/src/workos/pipes/models/update_custom_provider_definition.py new file mode 100644 index 00000000..ec40248c --- /dev/null +++ b/src/workos/pipes/models/update_custom_provider_definition.py @@ -0,0 +1,100 @@ +# 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, Dict, Optional +from workos._types import _raise_deserialize_error +from workos.common.models.update_custom_provider_definition_authenticate_via import ( + UpdateCustomProviderDefinitionAuthenticateVia, +) + + +@dataclass(slots=True) +class UpdateCustomProviderDefinition: + """Update Custom Provider Definition model.""" + + name: Optional[str] = None + """A descriptive name for the custom provider.""" + authorization_url: Optional[str] = None + """The provider's OAuth authorization endpoint.""" + token_url: Optional[str] = None + """The provider's OAuth token endpoint.""" + refresh_token_url: Optional[str] = None + """The endpoint used to refresh tokens, if different from the token endpoint.""" + pkce_enabled: Optional[bool] = None + """Whether PKCE is used during the authorization code flow.""" + request_scope_separator: Optional[str] = None + """The separator used to join requested scopes.""" + scopes_required: Optional[bool] = None + """Whether at least one scope must be selected when connecting an account.""" + client_secret_required: Optional[bool] = None + """Whether a client secret is required for this provider.""" + additional_authorization_parameters: Optional[Dict[str, str]] = None + """Additional static query parameters appended to the authorization request.""" + token_body_content_type: Optional[str] = None + """The Content-Type used when exchanging the token request.""" + authenticate_via: Optional["UpdateCustomProviderDefinitionAuthenticateVia"] = None + """How client credentials are sent when exchanging authorization codes and refreshing tokens.""" + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "UpdateCustomProviderDefinition": + """Deserialize from a dictionary.""" + try: + return cls( + name=data.get("name"), + authorization_url=data.get("authorization_url"), + token_url=data.get("token_url"), + refresh_token_url=data.get("refresh_token_url"), + pkce_enabled=data.get("pkce_enabled"), + request_scope_separator=data.get("request_scope_separator"), + scopes_required=data.get("scopes_required"), + client_secret_required=data.get("client_secret_required"), + additional_authorization_parameters=data.get( + "additional_authorization_parameters" + ), + token_body_content_type=data.get("token_body_content_type"), + authenticate_via=UpdateCustomProviderDefinitionAuthenticateVia( + _v_authenticate_via + ) + if (_v_authenticate_via := data.get("authenticate_via")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("UpdateCustomProviderDefinition", e) + + def to_dict(self) -> Dict[str, Any]: + """Serialize to a dictionary.""" + result: Dict[str, Any] = {} + if self.name is not None: + result["name"] = self.name + if self.authorization_url is not None: + result["authorization_url"] = self.authorization_url + if self.token_url is not None: + result["token_url"] = self.token_url + if self.refresh_token_url is not None: + result["refresh_token_url"] = self.refresh_token_url + else: + result["refresh_token_url"] = None + if self.pkce_enabled is not None: + result["pkce_enabled"] = self.pkce_enabled + if self.request_scope_separator is not None: + result["request_scope_separator"] = self.request_scope_separator + if self.scopes_required is not None: + result["scopes_required"] = self.scopes_required + if self.client_secret_required is not None: + result["client_secret_required"] = self.client_secret_required + if self.additional_authorization_parameters is not None: + result["additional_authorization_parameters"] = ( + self.additional_authorization_parameters + ) + if self.token_body_content_type is not None: + result["token_body_content_type"] = self.token_body_content_type + if self.authenticate_via is not None: + result["authenticate_via"] = ( + self.authenticate_via.value + if isinstance(self.authenticate_via, Enum) + else self.authenticate_via + ) + return result diff --git a/src/workos/pipes/models/update_data_integration.py b/src/workos/pipes/models/update_data_integration.py new file mode 100644 index 00000000..6d7e957a --- /dev/null +++ b/src/workos/pipes/models/update_data_integration.py @@ -0,0 +1,68 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import cast +from typing import Any, Dict, List, Optional +from workos._types import _raise_deserialize_error + +from .data_integration_credentials_dto import DataIntegrationCredentialsDto +from .update_custom_provider_definition import UpdateCustomProviderDefinition + + +@dataclass(slots=True) +class UpdateDataIntegration: + """Update Data Integration model.""" + + description: Optional[str] = None + """An optional description of the Data Integration.""" + enabled: Optional[bool] = None + """Whether the Data Integration is enabled.""" + scopes: Optional[List[str]] = None + """The OAuth scopes to request for the Data Integration. Pass `null` to reset to the provider's configured scopes.""" + credentials: Optional["DataIntegrationCredentialsDto"] = None + """New credentials for the Data Integration. When provided, rotates the stored client secret.""" + custom_provider: Optional["UpdateCustomProviderDefinition"] = None + """Updates to a custom provider's OAuth definition. Only valid for custom-provider integrations.""" + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "UpdateDataIntegration": + """Deserialize from a dictionary.""" + try: + return cls( + description=data.get("description"), + enabled=data.get("enabled"), + scopes=data.get("scopes"), + credentials=DataIntegrationCredentialsDto.from_dict( + cast(Dict[str, Any], _v_credentials) + ) + if (_v_credentials := data.get("credentials")) is not None + else None, + custom_provider=UpdateCustomProviderDefinition.from_dict( + cast(Dict[str, Any], _v_custom_provider) + ) + if (_v_custom_provider := data.get("custom_provider")) is not None + else None, + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("UpdateDataIntegration", e) + + def to_dict(self) -> Dict[str, Any]: + """Serialize to a dictionary.""" + result: Dict[str, Any] = {} + if self.description is not None: + result["description"] = self.description + else: + result["description"] = None + if self.enabled is not None: + result["enabled"] = self.enabled + if self.scopes is not None: + result["scopes"] = self.scopes + else: + result["scopes"] = None + if self.credentials is not None: + result["credentials"] = self.credentials.to_dict() + if self.custom_provider is not None: + result["custom_provider"] = self.custom_provider.to_dict() + return result diff --git a/tests/fixtures/connected_account_dto.json b/tests/fixtures/connected_account_dto.json new file mode 100644 index 00000000..d04b7e83 --- /dev/null +++ b/tests/fixtures/connected_account_dto.json @@ -0,0 +1,10 @@ +{ + "access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a", + "refresh_token": "ghr_xxxxxxxxxxxxxxxxxxxx", + "expires_at": "2025-12-31T23:59:59.000Z", + "scopes": [ + "repo", + "user:email" + ], + "state": "connected" +} diff --git a/tests/fixtures/create_data_integration.json b/tests/fixtures/create_data_integration.json new file mode 100644 index 00000000..8051c136 --- /dev/null +++ b/tests/fixtures/create_data_integration.json @@ -0,0 +1,29 @@ +{ + "provider": "github", + "description": "Production GitHub app", + "enabled": true, + "scopes": [ + "repo", + "read:org" + ], + "credentials": { + "type": "custom", + "client_id": "Iv1.abc123", + "client_secret": "secret_…" + }, + "custom_provider": { + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": "https://provider.example.com/oauth/token", + "pkce_enabled": true, + "request_scope_separator": " ", + "scopes_required": false, + "client_secret_required": true, + "additional_authorization_parameters": { + "prompt": "consent" + }, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "request_body" + } +} diff --git a/tests/fixtures/custom_provider_definition.json b/tests/fixtures/custom_provider_definition.json new file mode 100644 index 00000000..9909168b --- /dev/null +++ b/tests/fixtures/custom_provider_definition.json @@ -0,0 +1,15 @@ +{ + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": "https://provider.example.com/oauth/token", + "pkce_enabled": true, + "request_scope_separator": " ", + "scopes_required": false, + "client_secret_required": true, + "additional_authorization_parameters": { + "prompt": "consent" + }, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "request_body" +} diff --git a/tests/fixtures/data_integration.json b/tests/fixtures/data_integration.json new file mode 100644 index 00000000..997363ae --- /dev/null +++ b/tests/fixtures/data_integration.json @@ -0,0 +1,36 @@ +{ + "object": "data_integration", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "slug": "github", + "integration_type": "github", + "description": "Production GitHub app", + "enabled": true, + "state": "valid", + "scopes": [ + "repo", + "read:org" + ], + "redirect_uri": "https://api.workos.com/data-integrations/github/dik_01EHZNVPK3SFK441A1RGBFSHRT/callback", + "credentials": { + "type": "custom", + "client_id": "Iv1.abc123", + "redacted_client_secret": "6789" + }, + "custom_provider": { + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": "https://provider.example.com/oauth/token", + "pkce_enabled": true, + "request_scope_separator": " ", + "scopes_required": false, + "client_secret_required": true, + "additional_authorization_parameters": { + "prompt": "consent" + }, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "request_body" + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/data_integration_credential.json b/tests/fixtures/data_integration_credential.json new file mode 100644 index 00000000..60dfe77a --- /dev/null +++ b/tests/fixtures/data_integration_credential.json @@ -0,0 +1,5 @@ +{ + "type": "custom", + "client_id": "Iv1.abc123", + "redacted_client_secret": "6789" +} diff --git a/tests/fixtures/data_integration_credentials_dto.json b/tests/fixtures/data_integration_credentials_dto.json new file mode 100644 index 00000000..e5098765 --- /dev/null +++ b/tests/fixtures/data_integration_credentials_dto.json @@ -0,0 +1,5 @@ +{ + "type": "custom", + "client_id": "Iv1.abc123", + "client_secret": "secret_…" +} diff --git a/tests/fixtures/data_integration_custom_provider.json b/tests/fixtures/data_integration_custom_provider.json new file mode 100644 index 00000000..9909168b --- /dev/null +++ b/tests/fixtures/data_integration_custom_provider.json @@ -0,0 +1,15 @@ +{ + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": "https://provider.example.com/oauth/token", + "pkce_enabled": true, + "request_scope_separator": " ", + "scopes_required": false, + "client_secret_required": true, + "additional_authorization_parameters": { + "prompt": "consent" + }, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "request_body" +} diff --git a/tests/fixtures/list_data_integration.json b/tests/fixtures/list_data_integration.json new file mode 100644 index 00000000..13639f77 --- /dev/null +++ b/tests/fixtures/list_data_integration.json @@ -0,0 +1,44 @@ +{ + "data": [ + { + "object": "data_integration", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "slug": "github", + "integration_type": "github", + "description": "Production GitHub app", + "enabled": true, + "state": "valid", + "scopes": [ + "repo", + "read:org" + ], + "redirect_uri": "https://api.workos.com/data-integrations/github/dik_01EHZNVPK3SFK441A1RGBFSHRT/callback", + "credentials": { + "type": "custom", + "client_id": "Iv1.abc123", + "redacted_client_secret": "6789" + }, + "custom_provider": { + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": "https://provider.example.com/oauth/token", + "pkce_enabled": true, + "request_scope_separator": " ", + "scopes_required": false, + "client_secret_required": true, + "additional_authorization_parameters": { + "prompt": "consent" + }, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "request_body" + }, + "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/update_custom_provider_definition.json b/tests/fixtures/update_custom_provider_definition.json new file mode 100644 index 00000000..9909168b --- /dev/null +++ b/tests/fixtures/update_custom_provider_definition.json @@ -0,0 +1,15 @@ +{ + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": "https://provider.example.com/oauth/token", + "pkce_enabled": true, + "request_scope_separator": " ", + "scopes_required": false, + "client_secret_required": true, + "additional_authorization_parameters": { + "prompt": "consent" + }, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "request_body" +} diff --git a/tests/fixtures/update_data_integration.json b/tests/fixtures/update_data_integration.json new file mode 100644 index 00000000..8996a29c --- /dev/null +++ b/tests/fixtures/update_data_integration.json @@ -0,0 +1,28 @@ +{ + "description": "Production GitHub app", + "enabled": true, + "scopes": [ + "repo", + "read:org" + ], + "credentials": { + "type": "custom", + "client_id": "Iv1.abc123", + "client_secret": "secret_…" + }, + "custom_provider": { + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": "https://provider.example.com/oauth/token", + "pkce_enabled": true, + "request_scope_separator": " ", + "scopes_required": false, + "client_secret_required": true, + "additional_authorization_parameters": { + "prompt": "consent" + }, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "request_body" + } +} diff --git a/tests/test_common_models_round_trip.py b/tests/test_common_models_round_trip.py index 0980407c..d30a4ced 100644 --- a/tests/test_common_models_round_trip.py +++ b/tests/test_common_models_round_trip.py @@ -4,155 +4,24 @@ from tests.generated_helpers import load_fixture -from workos.common.models import ( - ConnectApplicationM2M, - ConnectApplicationOAuth, - ConnectApplicationOAuthRedirectUris, -) +from workos.common.models import AuthMethodMismatchError class TestModelRoundTrip: - def test_connect_application_oauth_round_trip(self): - data = load_fixture("connect_application_oauth.json") - instance = ConnectApplicationOAuth.from_dict(data) + def test_auth_method_mismatch_error_round_trip(self): + data = load_fixture("auth_method_mismatch_error.json") + instance = AuthMethodMismatchError.from_dict(data) serialized = instance.to_dict() assert serialized == data - restored = ConnectApplicationOAuth.from_dict(serialized) + restored = AuthMethodMismatchError.from_dict(serialized) assert restored.to_dict() == serialized - def test_connect_application_oauth_minimal_payload(self): + def test_auth_method_mismatch_error_minimal_payload(self): data = { - "object": "connect_application", - "id": "conn_app_01HXYZ123456789ABCDEFGHIJ", - "client_id": "client_01HXYZ123456789ABCDEFGHIJ", - "description": None, - "name": "My Application", - "scopes": ["openid", "profile", "email"], - "created_at": "2026-01-15T12:00:00.000Z", - "updated_at": "2026-01-15T12:00:00.000Z", - "application_type": "oauth", - "redirect_uris": [{"uri": "https://example.com/callback", "default": True}], - "uses_pkce": True, - "is_first_party": True, + "code": "auth_method_mismatch", + "message": "This installation uses oauth authentication. Use the POST /:slug/token endpoint instead.", } - instance = ConnectApplicationOAuth.from_dict(data) + instance = AuthMethodMismatchError.from_dict(data) serialized = instance.to_dict() - assert serialized["object"] == data["object"] - assert serialized["id"] == data["id"] - assert serialized["client_id"] == data["client_id"] - assert serialized["description"] == data["description"] - assert serialized["name"] == data["name"] - assert serialized["scopes"] == data["scopes"] - assert serialized["created_at"] == data["created_at"] - assert serialized["updated_at"] == data["updated_at"] - assert serialized["application_type"] == data["application_type"] - assert serialized["redirect_uris"] == data["redirect_uris"] - assert serialized["uses_pkce"] == data["uses_pkce"] - assert serialized["is_first_party"] == data["is_first_party"] - - def test_connect_application_oauth_omits_absent_optional_non_nullable_fields(self): - data = { - "object": "connect_application", - "id": "conn_app_01HXYZ123456789ABCDEFGHIJ", - "client_id": "client_01HXYZ123456789ABCDEFGHIJ", - "description": "An application for managing user access", - "name": "My Application", - "scopes": ["openid", "profile", "email"], - "created_at": "2026-01-15T12:00:00.000Z", - "updated_at": "2026-01-15T12:00:00.000Z", - "application_type": "oauth", - "redirect_uris": [{"uri": "https://example.com/callback", "default": True}], - "uses_pkce": True, - "is_first_party": True, - } - instance = ConnectApplicationOAuth.from_dict(data) - serialized = instance.to_dict() - assert "was_dynamically_registered" not in serialized - assert "organization_id" not in serialized - - def test_connect_application_oauth_preserves_nullable_fields(self): - data = { - "object": "connect_application", - "id": "conn_app_01HXYZ123456789ABCDEFGHIJ", - "client_id": "client_01HXYZ123456789ABCDEFGHIJ", - "description": None, - "name": "My Application", - "scopes": ["openid", "profile", "email"], - "created_at": "2026-01-15T12:00:00.000Z", - "updated_at": "2026-01-15T12:00:00.000Z", - "application_type": "oauth", - "redirect_uris": [{"uri": "https://example.com/callback", "default": True}], - "uses_pkce": True, - "is_first_party": True, - "was_dynamically_registered": True, - "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT", - } - instance = ConnectApplicationOAuth.from_dict(data) - serialized = instance.to_dict() - assert serialized["description"] is None - - def test_connect_application_oauth_redirect_uris_round_trip(self): - data = load_fixture("connect_application_oauth_redirect_uris.json") - instance = ConnectApplicationOAuthRedirectUris.from_dict(data) - serialized = instance.to_dict() - assert serialized == data - restored = ConnectApplicationOAuthRedirectUris.from_dict(serialized) - assert restored.to_dict() == serialized - - def test_connect_application_oauth_redirect_uris_minimal_payload(self): - data = {"uri": "https://example.com/callback", "default": True} - instance = ConnectApplicationOAuthRedirectUris.from_dict(data) - serialized = instance.to_dict() - assert serialized["uri"] == data["uri"] - assert serialized["default"] == data["default"] - - def test_connect_application_m2m_round_trip(self): - data = load_fixture("connect_application_m2m.json") - instance = ConnectApplicationM2M.from_dict(data) - serialized = instance.to_dict() - assert serialized == data - restored = ConnectApplicationM2M.from_dict(serialized) - assert restored.to_dict() == serialized - - def test_connect_application_m2m_minimal_payload(self): - data = { - "object": "connect_application", - "id": "conn_app_01HXYZ123456789ABCDEFGHIJ", - "client_id": "client_01HXYZ123456789ABCDEFGHIJ", - "description": None, - "name": "My Application", - "scopes": ["openid", "profile", "email"], - "created_at": "2026-01-15T12:00:00.000Z", - "updated_at": "2026-01-15T12:00:00.000Z", - "application_type": "m2m", - "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT", - } - instance = ConnectApplicationM2M.from_dict(data) - serialized = instance.to_dict() - assert serialized["object"] == data["object"] - assert serialized["id"] == data["id"] - assert serialized["client_id"] == data["client_id"] - assert serialized["description"] == data["description"] - assert serialized["name"] == data["name"] - assert serialized["scopes"] == data["scopes"] - assert serialized["created_at"] == data["created_at"] - assert serialized["updated_at"] == data["updated_at"] - assert serialized["application_type"] == data["application_type"] - assert serialized["organization_id"] == data["organization_id"] - - def test_connect_application_m2m_preserves_nullable_fields(self): - data = { - "object": "connect_application", - "id": "conn_app_01HXYZ123456789ABCDEFGHIJ", - "client_id": "client_01HXYZ123456789ABCDEFGHIJ", - "description": None, - "name": "My Application", - "scopes": ["openid", "profile", "email"], - "created_at": "2026-01-15T12:00:00.000Z", - "updated_at": "2026-01-15T12:00:00.000Z", - "application_type": "m2m", - "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT", - } - instance = ConnectApplicationM2M.from_dict(data) - serialized = instance.to_dict() - assert serialized["description"] is None + assert serialized["code"] == data["code"] + assert serialized["message"] == data["message"] diff --git a/tests/test_pipes.py b/tests/test_pipes.py index 5264db7f..e27dff80 100644 --- a/tests/test_pipes.py +++ b/tests/test_pipes.py @@ -6,13 +6,15 @@ from workos import WorkOSClient, AsyncWorkOSClient from tests.generated_helpers import load_fixture -from workos.common.models import ConnectedAccount +from workos.common.models import ConnectedAccount, PaginationOrder from workos.pipes.models import ( + DataIntegration, DataIntegrationAccessTokenResponse, DataIntegrationAuthorizeUrlResponse, DataIntegrationCredentialsResponse, DataIntegrationsListResponse, ) +from workos._pagination import AsyncPage, SyncPage from workos._errors import ( AuthenticationError, BadRequestError, @@ -24,6 +26,81 @@ class TestPipes: + def test_list_data_integrations(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("list_data_integration.json"), + ) + page = workos.pipes.list_data_integrations() + assert isinstance(page, SyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], DataIntegration) + + def test_list_data_integrations_empty_page(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = workos.pipes.list_data_integrations() + assert isinstance(page, SyncPage) + assert page.data == [] + + def test_list_data_integrations_encodes_query_params(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + workos.pipes.list_data_integrations( + 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_data_integration(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("data_integration.json"), + ) + result = workos.pipes.create_data_integration(provider="test_provider") + assert isinstance(result, DataIntegration) + assert result.object == "data_integration" + assert result.id == "data_integration_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/data-integrations") + body = json.loads(request.content) + assert body["provider"] == "test_provider" + + def test_get_data_integration(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("data_integration.json"), + ) + result = workos.pipes.get_data_integration("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") + + def test_update_data_integration(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("data_integration.json"), + ) + result = workos.pipes.update_data_integration("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") + + def test_delete_data_integration(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = workos.pipes.delete_data_integration("test_slug") + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith("/data-integrations/test_slug") + def test_update_data_integration_api_key(self, workos, httpx_mock): httpx_mock.add_response( json=load_fixture("connected_account.json"), @@ -111,6 +188,54 @@ def test_get_user_connected_account_encodes_query_params(self, workos, httpx_moc request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + def test_create_user_connected_account(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("connected_account.json"), + ) + result = workos.pipes.create_user_connected_account("test_user_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( + "/user_management/users/test_user_id/connected_accounts/test_slug" + ) + + def test_create_user_connected_account_encodes_query_params( + self, workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + workos.pipes.create_user_connected_account( + "test_user_id", "test_slug", organization_id="value organization_id/test" + ) + request = httpx_mock.get_request() + assert request.url.params["organization_id"] == "value organization_id/test" + + def test_update_user_connected_account(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("connected_account.json"), + ) + result = workos.pipes.update_user_connected_account("test_user_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( + "/user_management/users/test_user_id/connected_accounts/test_slug" + ) + + def test_update_user_connected_account_encodes_query_params( + self, workos, httpx_mock + ): + 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" + ) + request = httpx_mock.get_request() + assert request.url.params["organization_id"] == "value organization_id/test" + def test_delete_user_connected_account(self, workos, httpx_mock): httpx_mock.add_response(status_code=204) result = workos.pipes.delete_user_connected_account("test_user_id", "test_slug") @@ -154,43 +279,34 @@ def test_list_user_data_providers_encodes_query_params(self, workos, httpx_mock) request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" - def test_update_data_integration_api_key_with_request_options( - self, workos, httpx_mock - ): - httpx_mock.add_response(json=load_fixture("connected_account.json")) - workos.pipes.update_data_integration_api_key( - "test_slug", - user_id="test_user_id", - secret="test_secret", - request_options={"extra_headers": {"X-Custom": "value"}}, + def test_list_data_integrations_with_request_options(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + workos.pipes.list_data_integrations( + request_options={"extra_headers": {"X-Custom": "value"}} ) request = httpx_mock.get_request() assert request.headers["X-Custom"] == "value" - def test_update_data_integration_api_key_unauthorized(self, workos, httpx_mock): + def test_list_data_integrations_unauthorized(self, workos, httpx_mock): httpx_mock.add_response( status_code=401, json={"message": "Unauthorized"}, ) with pytest.raises(AuthenticationError): - workos.pipes.update_data_integration_api_key( - "test_slug", user_id="test_user_id", secret="test_secret" - ) + workos.pipes.list_data_integrations() - def test_update_data_integration_api_key_not_found(self, httpx_mock): + def test_list_data_integrations_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.pipes.update_data_integration_api_key( - "test_slug", user_id="test_user_id", secret="test_secret" - ) + workos.pipes.list_data_integrations() finally: workos.close() - def test_update_data_integration_api_key_rate_limited(self, httpx_mock): + def test_list_data_integrations_rate_limited(self, httpx_mock): workos = WorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) @@ -201,53 +317,121 @@ def test_update_data_integration_api_key_rate_limited(self, httpx_mock): json={"message": "Slow down"}, ) with pytest.raises(RateLimitExceededError): - workos.pipes.update_data_integration_api_key( - "test_slug", user_id="test_user_id", secret="test_secret" - ) + workos.pipes.list_data_integrations() finally: workos.close() - def test_update_data_integration_api_key_server_error(self, httpx_mock): + def test_list_data_integrations_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.pipes.update_data_integration_api_key( - "test_slug", user_id="test_user_id", secret="test_secret" - ) + workos.pipes.list_data_integrations() finally: workos.close() - def test_update_data_integration_api_key_bad_request(self, httpx_mock): + def test_list_data_integrations_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.pipes.update_data_integration_api_key( - "test_slug", user_id="test_user_id", secret="test_secret" - ) + workos.pipes.list_data_integrations() finally: workos.close() - def test_update_data_integration_api_key_unprocessable(self, httpx_mock): + def test_list_data_integrations_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.pipes.update_data_integration_api_key( - "test_slug", user_id="test_user_id", secret="test_secret" - ) + workos.pipes.list_data_integrations() finally: workos.close() class TestAsyncPipes: + @pytest.mark.asyncio + async def test_list_data_integrations(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("list_data_integration.json")) + page = await async_workos.pipes.list_data_integrations() + assert isinstance(page, AsyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], DataIntegration) + + @pytest.mark.asyncio + async def test_list_data_integrations_empty_page(self, async_workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = await async_workos.pipes.list_data_integrations() + assert isinstance(page, AsyncPage) + assert page.data == [] + + @pytest.mark.asyncio + async def test_list_data_integrations_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + await async_workos.pipes.list_data_integrations( + 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_data_integration(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("data_integration.json")) + result = await async_workos.pipes.create_data_integration( + provider="test_provider" + ) + assert isinstance(result, DataIntegration) + assert result.object == "data_integration" + assert result.id == "data_integration_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/data-integrations") + + @pytest.mark.asyncio + async def test_get_data_integration(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("data_integration.json")) + result = await async_workos.pipes.get_data_integration("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") + + @pytest.mark.asyncio + async def test_update_data_integration(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("data_integration.json")) + result = await async_workos.pipes.update_data_integration("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") + + @pytest.mark.asyncio + async def test_delete_data_integration(self, async_workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = await async_workos.pipes.delete_data_integration("test_slug") + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith("/data-integrations/test_slug") + @pytest.mark.asyncio async def test_update_data_integration_api_key(self, async_workos, httpx_mock): httpx_mock.add_response(json=load_fixture("connected_account.json")) @@ -334,6 +518,58 @@ async def test_get_user_connected_account_encodes_query_params( request = httpx_mock.get_request() assert request.url.params["organization_id"] == "value organization_id/test" + @pytest.mark.asyncio + async def test_create_user_connected_account(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + result = await async_workos.pipes.create_user_connected_account( + "test_user_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( + "/user_management/users/test_user_id/connected_accounts/test_slug" + ) + + @pytest.mark.asyncio + async def test_create_user_connected_account_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + await async_workos.pipes.create_user_connected_account( + "test_user_id", "test_slug", organization_id="value organization_id/test" + ) + request = httpx_mock.get_request() + assert request.url.params["organization_id"] == "value organization_id/test" + + @pytest.mark.asyncio + async def test_update_user_connected_account(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("connected_account.json")) + result = await async_workos.pipes.update_user_connected_account( + "test_user_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( + "/user_management/users/test_user_id/connected_accounts/test_slug" + ) + + @pytest.mark.asyncio + async def test_update_user_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_user_connected_account( + "test_user_id", "test_slug", organization_id="value organization_id/test" + ) + request = httpx_mock.get_request() + assert request.url.params["organization_id"] == "value organization_id/test" + @pytest.mark.asyncio async def test_delete_user_connected_account(self, async_workos, httpx_mock): httpx_mock.add_response(status_code=204) @@ -386,45 +622,36 @@ async def test_list_user_data_providers_encodes_query_params( assert request.url.params["organization_id"] == "value organization_id/test" @pytest.mark.asyncio - async def test_update_data_integration_api_key_with_request_options( + async def test_list_data_integrations_with_request_options( self, async_workos, httpx_mock ): - httpx_mock.add_response(json=load_fixture("connected_account.json")) - await async_workos.pipes.update_data_integration_api_key( - "test_slug", - user_id="test_user_id", - secret="test_secret", - request_options={"extra_headers": {"X-Custom": "value"}}, + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + await async_workos.pipes.list_data_integrations( + request_options={"extra_headers": {"X-Custom": "value"}} ) request = httpx_mock.get_request() assert request.headers["X-Custom"] == "value" @pytest.mark.asyncio - async def test_update_data_integration_api_key_unauthorized( - self, async_workos, httpx_mock - ): + async def test_list_data_integrations_unauthorized(self, async_workos, httpx_mock): httpx_mock.add_response(status_code=401, json={"message": "Unauthorized"}) with pytest.raises(AuthenticationError): - await async_workos.pipes.update_data_integration_api_key( - "test_slug", user_id="test_user_id", secret="test_secret" - ) + await async_workos.pipes.list_data_integrations() @pytest.mark.asyncio - async def test_update_data_integration_api_key_not_found(self, httpx_mock): + async def test_list_data_integrations_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.pipes.update_data_integration_api_key( - "test_slug", user_id="test_user_id", secret="test_secret" - ) + await workos.pipes.list_data_integrations() finally: await workos.close() @pytest.mark.asyncio - async def test_update_data_integration_api_key_rate_limited(self, httpx_mock): + async def test_list_data_integrations_rate_limited(self, httpx_mock): workos = AsyncWorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) @@ -435,50 +662,42 @@ async def test_update_data_integration_api_key_rate_limited(self, httpx_mock): json={"message": "Slow down"}, ) with pytest.raises(RateLimitExceededError): - await workos.pipes.update_data_integration_api_key( - "test_slug", user_id="test_user_id", secret="test_secret" - ) + await workos.pipes.list_data_integrations() finally: await workos.close() @pytest.mark.asyncio - async def test_update_data_integration_api_key_server_error(self, httpx_mock): + async def test_list_data_integrations_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.pipes.update_data_integration_api_key( - "test_slug", user_id="test_user_id", secret="test_secret" - ) + await workos.pipes.list_data_integrations() finally: await workos.close() @pytest.mark.asyncio - async def test_update_data_integration_api_key_bad_request(self, httpx_mock): + async def test_list_data_integrations_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.pipes.update_data_integration_api_key( - "test_slug", user_id="test_user_id", secret="test_secret" - ) + await workos.pipes.list_data_integrations() finally: await workos.close() @pytest.mark.asyncio - async def test_update_data_integration_api_key_unprocessable(self, httpx_mock): + async def test_list_data_integrations_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.pipes.update_data_integration_api_key( - "test_slug", user_id="test_user_id", secret="test_secret" - ) + await workos.pipes.list_data_integrations() finally: await workos.close() diff --git a/tests/test_pipes_models_round_trip.py b/tests/test_pipes_models_round_trip.py new file mode 100644 index 00000000..3477f8a8 --- /dev/null +++ b/tests/test_pipes_models_round_trip.py @@ -0,0 +1,933 @@ +# 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.pipes.models import ( + ConnectedAccount, + CustomProviderDefinition, + DataIntegration, + DataIntegrationAccessTokenResponse, + DataIntegrationAccessTokenResponseAccessToken, + DataIntegrationAuthorizeUrlResponse, + DataIntegrationCredential, + DataIntegrationCredentialsDto, + DataIntegrationCredentialsResponse, + DataIntegrationCredentialsResponseCredential, + DataIntegrationCustomProvider, + DataIntegrationsListResponse, + DataIntegrationsListResponseData, + DataIntegrationsListResponseDataConnectedAccount, + UpdateCustomProviderDefinition, +) + + +class TestModelRoundTrip: + def test_data_integration_credentials_dto_round_trip(self): + data = load_fixture("data_integration_credentials_dto.json") + instance = DataIntegrationCredentialsDto.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationCredentialsDto.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integration_credentials_dto_minimal_payload(self): + data = {"type": "custom"} + instance = DataIntegrationCredentialsDto.from_dict(data) + serialized = instance.to_dict() + assert serialized["type"] == data["type"] + + def test_data_integration_credentials_dto_omits_absent_optional_non_nullable_fields( + self, + ): + data = {"type": "custom"} + instance = DataIntegrationCredentialsDto.from_dict(data) + serialized = instance.to_dict() + assert "client_id" not in serialized + assert "client_secret" not in serialized + + def test_data_integration_credentials_dto_round_trips_unknown_enum_values(self): + data = { + "type": "unexpected_data_integration_credentials_dto_type", + "client_id": "Iv1.abc123", + "client_secret": "secret_…", + } + instance = DataIntegrationCredentialsDto.from_dict(data) + assert instance.to_dict() == data + + def test_custom_provider_definition_round_trip(self): + data = load_fixture("custom_provider_definition.json") + instance = CustomProviderDefinition.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = CustomProviderDefinition.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_custom_provider_definition_minimal_payload(self): + data = { + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + } + instance = CustomProviderDefinition.from_dict(data) + serialized = instance.to_dict() + assert serialized["name"] == data["name"] + assert serialized["authorization_url"] == data["authorization_url"] + assert serialized["token_url"] == data["token_url"] + + def test_custom_provider_definition_omits_absent_optional_non_nullable_fields(self): + data = { + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": "https://provider.example.com/oauth/token", + } + instance = CustomProviderDefinition.from_dict(data) + serialized = instance.to_dict() + assert "pkce_enabled" not in serialized + assert "request_scope_separator" not in serialized + assert "scopes_required" not in serialized + assert "client_secret_required" not in serialized + assert "additional_authorization_parameters" not in serialized + assert "token_body_content_type" not in serialized + assert "authenticate_via" not in serialized + + def test_custom_provider_definition_preserves_nullable_fields(self): + data = { + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": None, + "pkce_enabled": True, + "request_scope_separator": " ", + "scopes_required": False, + "client_secret_required": True, + "additional_authorization_parameters": {"prompt": "consent"}, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "request_body", + } + instance = CustomProviderDefinition.from_dict(data) + serialized = instance.to_dict() + assert serialized["refresh_token_url"] is None + + def test_custom_provider_definition_round_trips_unknown_enum_values(self): + data = { + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": "https://provider.example.com/oauth/token", + "pkce_enabled": True, + "request_scope_separator": " ", + "scopes_required": False, + "client_secret_required": True, + "additional_authorization_parameters": {"prompt": "consent"}, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "unexpected_custom_provider_definition_authenticate_via", + } + instance = CustomProviderDefinition.from_dict(data) + assert instance.to_dict() == data + + def test_update_custom_provider_definition_round_trip(self): + data = load_fixture("update_custom_provider_definition.json") + instance = UpdateCustomProviderDefinition.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = UpdateCustomProviderDefinition.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_update_custom_provider_definition_minimal_payload(self): + data = {} + instance = UpdateCustomProviderDefinition.from_dict(data) + assert instance.to_dict() is not None + + def test_update_custom_provider_definition_omits_absent_optional_non_nullable_fields( + self, + ): + data = {"refresh_token_url": "https://provider.example.com/oauth/token"} + instance = UpdateCustomProviderDefinition.from_dict(data) + serialized = instance.to_dict() + assert "name" not in serialized + assert "authorization_url" not in serialized + assert "token_url" not in serialized + assert "pkce_enabled" not in serialized + assert "request_scope_separator" not in serialized + assert "scopes_required" not in serialized + assert "client_secret_required" not in serialized + assert "additional_authorization_parameters" not in serialized + assert "token_body_content_type" not in serialized + assert "authenticate_via" not in serialized + + def test_update_custom_provider_definition_preserves_nullable_fields(self): + data = { + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": None, + "pkce_enabled": True, + "request_scope_separator": " ", + "scopes_required": False, + "client_secret_required": True, + "additional_authorization_parameters": {"prompt": "consent"}, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "request_body", + } + instance = UpdateCustomProviderDefinition.from_dict(data) + serialized = instance.to_dict() + assert serialized["refresh_token_url"] is None + + def test_update_custom_provider_definition_round_trips_unknown_enum_values(self): + data = { + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": "https://provider.example.com/oauth/token", + "pkce_enabled": True, + "request_scope_separator": " ", + "scopes_required": False, + "client_secret_required": True, + "additional_authorization_parameters": {"prompt": "consent"}, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "unexpected_update_custom_provider_definition_authenticate_via", + } + instance = UpdateCustomProviderDefinition.from_dict(data) + assert instance.to_dict() == data + + def test_data_integration_round_trip(self): + data = load_fixture("data_integration.json") + instance = DataIntegration.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegration.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integration_minimal_payload(self): + data = { + "object": "data_integration", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "slug": "github", + "integration_type": "github", + "description": None, + "enabled": True, + "state": "valid", + "scopes": None, + "redirect_uri": "https://api.workos.com/data-integrations/github/dik_01EHZNVPK3SFK441A1RGBFSHRT/callback", + "credentials": { + "type": "custom", + "client_id": "Iv1.abc123", + "redacted_client_secret": "6789", + }, + "custom_provider": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = DataIntegration.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["slug"] == data["slug"] + assert serialized["integration_type"] == data["integration_type"] + assert serialized["description"] == data["description"] + assert serialized["enabled"] == data["enabled"] + assert serialized["state"] == data["state"] + assert serialized["scopes"] == data["scopes"] + assert serialized["redirect_uri"] == data["redirect_uri"] + assert serialized["credentials"] == data["credentials"] + assert serialized["custom_provider"] == data["custom_provider"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] + + def test_data_integration_preserves_nullable_fields(self): + data = { + "object": "data_integration", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "slug": "github", + "integration_type": "github", + "description": None, + "enabled": True, + "state": "valid", + "scopes": None, + "redirect_uri": "https://api.workos.com/data-integrations/github/dik_01EHZNVPK3SFK441A1RGBFSHRT/callback", + "credentials": { + "type": "custom", + "client_id": "Iv1.abc123", + "redacted_client_secret": "6789", + }, + "custom_provider": None, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = DataIntegration.from_dict(data) + serialized = instance.to_dict() + assert serialized["description"] is None + assert serialized["scopes"] is None + assert serialized["custom_provider"] is None + + def test_data_integration_round_trips_unknown_enum_values(self): + data = { + "object": "data_integration", + "id": "data_integration_01EHZNVPK3SFK441A1RGBFSHRT", + "slug": "github", + "integration_type": "github", + "description": "Production GitHub app", + "enabled": True, + "state": "unexpected_data_integration_state", + "scopes": ["repo", "read:org"], + "redirect_uri": "https://api.workos.com/data-integrations/github/dik_01EHZNVPK3SFK441A1RGBFSHRT/callback", + "credentials": { + "type": "custom", + "client_id": "Iv1.abc123", + "redacted_client_secret": "6789", + }, + "custom_provider": { + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": "https://provider.example.com/oauth/token", + "pkce_enabled": True, + "request_scope_separator": " ", + "scopes_required": False, + "client_secret_required": True, + "additional_authorization_parameters": {"prompt": "consent"}, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "request_body", + }, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = DataIntegration.from_dict(data) + assert instance.to_dict() == data + + def test_data_integration_authorize_url_response_round_trip(self): + data = load_fixture("data_integration_authorize_url_response.json") + instance = DataIntegrationAuthorizeUrlResponse.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationAuthorizeUrlResponse.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integration_authorize_url_response_minimal_payload(self): + data = { + "url": "https://api.workos.com/data-integrations/q2czJKmVAraSBg8xFpT7M9uR/authorize-redirect" + } + instance = DataIntegrationAuthorizeUrlResponse.from_dict(data) + serialized = instance.to_dict() + assert serialized["url"] == data["url"] + + def test_data_integration_access_token_response_round_trip(self): + data = load_fixture("data_integration_access_token_response.json") + instance = DataIntegrationAccessTokenResponse.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationAccessTokenResponse.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integration_access_token_response_minimal_payload(self): + data = {} + instance = DataIntegrationAccessTokenResponse.from_dict(data) + assert instance.to_dict() is not None + + def test_data_integration_access_token_response_omits_absent_optional_non_nullable_fields( + self, + ): + data = {} + instance = DataIntegrationAccessTokenResponse.from_dict(data) + serialized = instance.to_dict() + assert "active" not in serialized + assert "access_token" not in serialized + assert "error" not in serialized + + def test_data_integration_access_token_response_round_trips_unknown_enum_values( + self, + ): + data = { + "active": True, + "access_token": { + "object": "access_token", + "access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a", + "expires_at": "2025-12-31T23:59:59.000Z", + "scopes": ["repo", "user:email"], + "missing_scopes": [], + }, + "error": "unexpected_data_integration_access_token_response_error", + } + instance = DataIntegrationAccessTokenResponse.from_dict(data) + assert instance.to_dict() == data + + def test_data_integration_credentials_response_round_trip(self): + data = load_fixture("data_integration_credentials_response.json") + instance = DataIntegrationCredentialsResponse.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationCredentialsResponse.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integration_credentials_response_minimal_payload(self): + data = {} + instance = DataIntegrationCredentialsResponse.from_dict(data) + assert instance.to_dict() is not None + + def test_data_integration_credentials_response_omits_absent_optional_non_nullable_fields( + self, + ): + data = {} + instance = DataIntegrationCredentialsResponse.from_dict(data) + serialized = instance.to_dict() + assert "active" not in serialized + assert "credential" not in serialized + assert "error" not in serialized + + def test_data_integration_credentials_response_round_trips_unknown_enum_values( + self, + ): + data = { + "active": True, + "credential": { + "object": "credential", + "auth_method": "oauth", + "value": "gho_16C7e42F292c6912E7710c838347Ae178B4a", + "expires_at": "2025-12-31T23:59:59.000Z", + "scopes": ["repo", "user:email"], + "missing_scopes": [], + }, + "error": "unexpected_data_integration_credentials_response_error", + } + instance = DataIntegrationCredentialsResponse.from_dict(data) + assert instance.to_dict() == data + + def test_connected_account_round_trip(self): + data = load_fixture("connected_account.json") + instance = ConnectedAccount.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = ConnectedAccount.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_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", + } + instance = ConnectedAccount.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"] + + def test_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, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + } + instance = ConnectedAccount.from_dict(data) + serialized = instance.to_dict() + assert "auth_method" not in serialized + + def test_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, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + } + instance = ConnectedAccount.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 + + def test_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_connected_account_auth_method", + "api_key_last_4": None, + "state": "connected", + "created_at": "2024-01-16T14:20:00.000Z", + "updated_at": "2024-01-16T14:20:00.000Z", + } + 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, + "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, + } + 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"] + + 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, + "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, + } + 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, + "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) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationCredentialsResponseCredential.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integration_credentials_response_credential_minimal_payload(self): + data = { + "object": "credential", + "auth_method": "oauth", + "value": "gho_16C7e42F292c6912E7710c838347Ae178B4a", + "expires_at": None, + "scopes": ["repo", "user:email"], + "missing_scopes": [], + } + instance = DataIntegrationCredentialsResponseCredential.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["auth_method"] == data["auth_method"] + assert serialized["value"] == data["value"] + assert serialized["expires_at"] == data["expires_at"] + assert serialized["scopes"] == data["scopes"] + assert serialized["missing_scopes"] == data["missing_scopes"] + + def test_data_integration_credentials_response_credential_preserves_nullable_fields( + self, + ): + data = { + "object": "credential", + "auth_method": "oauth", + "value": "gho_16C7e42F292c6912E7710c838347Ae178B4a", + "expires_at": None, + "scopes": ["repo", "user:email"], + "missing_scopes": [], + } + instance = DataIntegrationCredentialsResponseCredential.from_dict(data) + serialized = instance.to_dict() + assert serialized["expires_at"] is None + + def test_data_integration_access_token_response_access_token_round_trip(self): + data = load_fixture("data_integration_access_token_response_access_token.json") + instance = DataIntegrationAccessTokenResponseAccessToken.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationAccessTokenResponseAccessToken.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integration_access_token_response_access_token_minimal_payload(self): + data = { + "object": "access_token", + "access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a", + "expires_at": None, + "scopes": ["repo", "user:email"], + "missing_scopes": [], + } + instance = DataIntegrationAccessTokenResponseAccessToken.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["access_token"] == data["access_token"] + assert serialized["expires_at"] == data["expires_at"] + assert serialized["scopes"] == data["scopes"] + assert serialized["missing_scopes"] == data["missing_scopes"] + + def test_data_integration_access_token_response_access_token_preserves_nullable_fields( + self, + ): + data = { + "object": "access_token", + "access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a", + "expires_at": None, + "scopes": ["repo", "user:email"], + "missing_scopes": [], + } + instance = DataIntegrationAccessTokenResponseAccessToken.from_dict(data) + serialized = instance.to_dict() + assert serialized["expires_at"] is None + + def test_data_integration_credential_round_trip(self): + data = load_fixture("data_integration_credential.json") + instance = DataIntegrationCredential.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationCredential.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integration_credential_minimal_payload(self): + data = {"type": "custom", "client_id": None, "redacted_client_secret": None} + instance = DataIntegrationCredential.from_dict(data) + serialized = instance.to_dict() + assert serialized["type"] == data["type"] + assert serialized["client_id"] == data["client_id"] + assert serialized["redacted_client_secret"] == data["redacted_client_secret"] + + def test_data_integration_credential_preserves_nullable_fields(self): + data = {"type": "custom", "client_id": None, "redacted_client_secret": None} + instance = DataIntegrationCredential.from_dict(data) + serialized = instance.to_dict() + assert serialized["client_id"] is None + assert serialized["redacted_client_secret"] is None + + def test_data_integration_credential_round_trips_unknown_enum_values(self): + data = { + "type": "unexpected_data_integration_credential_type", + "client_id": "Iv1.abc123", + "redacted_client_secret": "6789", + } + instance = DataIntegrationCredential.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) + serialized = instance.to_dict() + assert serialized == data + restored = DataIntegrationCustomProvider.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_data_integration_custom_provider_minimal_payload(self): + data = { + "name": "My OAuth App", + "authorization_url": None, + "token_url": None, + "refresh_token_url": None, + "pkce_enabled": True, + "request_scope_separator": " ", + "scopes_required": False, + "client_secret_required": True, + "additional_authorization_parameters": {"prompt": "consent"}, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "request_body", + } + instance = DataIntegrationCustomProvider.from_dict(data) + serialized = instance.to_dict() + assert serialized["name"] == data["name"] + assert serialized["authorization_url"] == data["authorization_url"] + assert serialized["token_url"] == data["token_url"] + assert serialized["refresh_token_url"] == data["refresh_token_url"] + assert serialized["pkce_enabled"] == data["pkce_enabled"] + assert serialized["request_scope_separator"] == data["request_scope_separator"] + assert serialized["scopes_required"] == data["scopes_required"] + assert serialized["client_secret_required"] == data["client_secret_required"] + assert ( + serialized["additional_authorization_parameters"] + == data["additional_authorization_parameters"] + ) + assert serialized["token_body_content_type"] == data["token_body_content_type"] + assert serialized["authenticate_via"] == data["authenticate_via"] + + def test_data_integration_custom_provider_preserves_nullable_fields(self): + data = { + "name": "My OAuth App", + "authorization_url": None, + "token_url": None, + "refresh_token_url": None, + "pkce_enabled": True, + "request_scope_separator": " ", + "scopes_required": False, + "client_secret_required": True, + "additional_authorization_parameters": {"prompt": "consent"}, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "request_body", + } + instance = DataIntegrationCustomProvider.from_dict(data) + serialized = instance.to_dict() + assert serialized["authorization_url"] is None + assert serialized["token_url"] is None + assert serialized["refresh_token_url"] is None + + def test_data_integration_custom_provider_round_trips_unknown_enum_values(self): + data = { + "name": "My OAuth App", + "authorization_url": "https://provider.example.com/oauth/authorize", + "token_url": "https://provider.example.com/oauth/token", + "refresh_token_url": "https://provider.example.com/oauth/token", + "pkce_enabled": True, + "request_scope_separator": " ", + "scopes_required": False, + "client_secret_required": True, + "additional_authorization_parameters": {"prompt": "consent"}, + "token_body_content_type": "application/x-www-form-urlencoded", + "authenticate_via": "unexpected_data_integration_custom_provider_authenticate_via", + } + 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, + "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 + + 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, + "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["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, + "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