From 946786c210c4ba68a67f03e5510ce4dee466bc4f Mon Sep 17 00:00:00 2001 From: mattgd Date: Tue, 26 Nov 2024 10:00:14 -0500 Subject: [PATCH 1/2] Add GET /organization/:orgId/roles support. --- tests/test_organizations.py | 33 +++++++++++++++++++++++++++++++ tests/utils/fixtures/mock_role.py | 18 +++++++++++++++++ workos/organizations.py | 17 ++++++++++++++++ workos/types/roles/role.py | 22 ++++++++++++++++++++- 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tests/utils/fixtures/mock_role.py diff --git a/tests/test_organizations.py b/tests/test_organizations.py index fef868f7..60cb60a6 100644 --- a/tests/test_organizations.py +++ b/tests/test_organizations.py @@ -3,6 +3,7 @@ import pytest from tests.types.test_auto_pagination_function import TestAutoPaginationFunction from tests.utils.fixtures.mock_organization import MockOrganization +from tests.utils.fixtures.mock_role import MockRole from tests.utils.list_resource import list_response_of from tests.utils.syncify import syncify from workos.organizations import AsyncOrganizations, Organizations @@ -67,6 +68,13 @@ def mock_organizations_multiple_data_pages(self): ] return list_response_of(data=organizations_list) + @pytest.fixture + def mock_organization_roles(self): + return { + "data": [MockRole(id=str(i)).dict() for i in range(10)], + "object": "list", + } + def test_list_organizations( self, mock_organizations, capture_and_mock_http_client_request ): @@ -227,3 +235,28 @@ def test_list_organizations_auto_pagination_for_multiple_pages( list_function=self.organizations.list_organizations, expected_all_page_data=mock_organizations_multiple_data_pages["data"], ) + + def test_list_organization_roles( + self, mock_organization_roles, capture_and_mock_http_client_request + ): + request_kwargs = capture_and_mock_http_client_request( + self.http_client, mock_organization_roles, 200 + ) + + organization_roles_response = syncify( + self.organizations.list_organization_roles( + organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T" + ) + ) + + def to_dict(x): + return x.dict() + + assert request_kwargs["method"] == "get" + assert request_kwargs["url"].endswith( + "/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T/roles" + ) + assert ( + list(map(to_dict, organization_roles_response.data)) + == mock_organization_roles["data"] + ) diff --git a/tests/utils/fixtures/mock_role.py b/tests/utils/fixtures/mock_role.py new file mode 100644 index 00000000..63ae087c --- /dev/null +++ b/tests/utils/fixtures/mock_role.py @@ -0,0 +1,18 @@ +import datetime + +from workos.types.roles.role import OrganizationRole + + +class MockRole(OrganizationRole): + def __init__(self, id): + now = datetime.datetime.now().isoformat() + super().__init__( + object="role", + id=id, + name="Member", + slug="member", + description="The default member role", + type="EnvironmentRole", + created_at=now, + updated_at=now, + ) diff --git a/workos/organizations.py b/workos/organizations.py index 61ec9218..237a4a4a 100644 --- a/workos/organizations.py +++ b/workos/organizations.py @@ -2,6 +2,7 @@ from workos.types.organizations.domain_data_input import DomainDataInput from workos.types.organizations.list_filters import OrganizationListFilters +from workos.types.roles.role import OrganizationRole, RolesList from workos.typing.sync_or_async import SyncOrAsync from workos.utils.http_client import AsyncHTTPClient, SyncHTTPClient from workos.utils.pagination_order import PaginationOrder @@ -223,6 +224,14 @@ def delete_organization(self, organization_id: str) -> None: method=REQUEST_METHOD_DELETE, ) + def list_organization_roles(self, organization_id: str) -> RolesList: + response = self._http_client.request( + f"organizations/{organization_id}/roles", + method=REQUEST_METHOD_GET, + ) + + return RolesList.model_validate(response) + class AsyncOrganizations(OrganizationsModule): @@ -324,3 +333,11 @@ async def delete_organization(self, organization_id: str) -> None: f"organizations/{organization_id}", method=REQUEST_METHOD_DELETE, ) + + async def list_organization_roles(self, organization_id: str) -> RolesList: + response = await self._http_client.request( + f"organizations/{organization_id}/roles", + method=REQUEST_METHOD_GET, + ) + + return RolesList.model_validate(response) diff --git a/workos/types/roles/role.py b/workos/types/roles/role.py index 8b0886b8..f6023f13 100644 --- a/workos/types/roles/role.py +++ b/workos/types/roles/role.py @@ -1,8 +1,28 @@ from typing import Literal, Optional, Sequence from workos.types.workos_model import WorkOSModel +RoleType = Literal["EnvironmentRole", "OrganizationRole"] -class Role(WorkOSModel): + +class RoleCommon(WorkOSModel): object: Literal["role"] slug: str + + +# TODO: This is used for events/webhooks only. Rename to EventRole or something similar. +class Role(RoleCommon): permissions: Optional[Sequence[str]] = None + + +class OrganizationRole(RoleCommon): + id: str + name: str + description: Optional[str] = None + type: RoleType + created_at: str + updated_at: str + + +class RolesList(WorkOSModel): + object: Literal["list"] + data: Sequence[OrganizationRole] From 1066b67d90b1f674fe923fbb9338d9c945d421e5 Mon Sep 17 00:00:00 2001 From: mattgd Date: Mon, 30 Dec 2024 09:55:13 -0500 Subject: [PATCH 2/2] Updates to role naming. --- tests/utils/fixtures/mock_role.py | 4 ++-- workos/organizations.py | 10 +++++----- workos/types/events/event.py | 9 ++++----- workos/types/events/event_model.py | 4 ++-- workos/types/roles/role.py | 9 ++++----- workos/types/webhooks/webhook.py | 8 ++++---- 6 files changed, 21 insertions(+), 23 deletions(-) diff --git a/tests/utils/fixtures/mock_role.py b/tests/utils/fixtures/mock_role.py index 63ae087c..756e1af5 100644 --- a/tests/utils/fixtures/mock_role.py +++ b/tests/utils/fixtures/mock_role.py @@ -1,9 +1,9 @@ import datetime -from workos.types.roles.role import OrganizationRole +from workos.types.roles.role import Role -class MockRole(OrganizationRole): +class MockRole(Role): def __init__(self, id): now = datetime.datetime.now().isoformat() super().__init__( diff --git a/workos/organizations.py b/workos/organizations.py index 237a4a4a..6a2cb82a 100644 --- a/workos/organizations.py +++ b/workos/organizations.py @@ -2,7 +2,7 @@ from workos.types.organizations.domain_data_input import DomainDataInput from workos.types.organizations.list_filters import OrganizationListFilters -from workos.types.roles.role import OrganizationRole, RolesList +from workos.types.roles.role import RoleList from workos.typing.sync_or_async import SyncOrAsync from workos.utils.http_client import AsyncHTTPClient, SyncHTTPClient from workos.utils.pagination_order import PaginationOrder @@ -224,13 +224,13 @@ def delete_organization(self, organization_id: str) -> None: method=REQUEST_METHOD_DELETE, ) - def list_organization_roles(self, organization_id: str) -> RolesList: + def list_organization_roles(self, organization_id: str) -> RoleList: response = self._http_client.request( f"organizations/{organization_id}/roles", method=REQUEST_METHOD_GET, ) - return RolesList.model_validate(response) + return RoleList.model_validate(response) class AsyncOrganizations(OrganizationsModule): @@ -334,10 +334,10 @@ async def delete_organization(self, organization_id: str) -> None: method=REQUEST_METHOD_DELETE, ) - async def list_organization_roles(self, organization_id: str) -> RolesList: + async def list_organization_roles(self, organization_id: str) -> RoleList: response = await self._http_client.request( f"organizations/{organization_id}/roles", method=REQUEST_METHOD_GET, ) - return RolesList.model_validate(response) + return RoleList.model_validate(response) diff --git a/workos/types/events/event.py b/workos/types/events/event.py index 7f6d4513..51eaf5ef 100644 --- a/workos/types/events/event.py +++ b/workos/types/events/event.py @@ -27,7 +27,6 @@ ) from workos.types.events.directory_payload import DirectoryPayload from workos.types.events.directory_payload_with_legacy_fields import ( - DirectoryPayloadWithLegacyFields, DirectoryPayloadWithLegacyFieldsForEventsApi, ) from workos.types.events.directory_user_with_previous_attributes import ( @@ -40,7 +39,7 @@ from workos.types.events.session_created_payload import SessionCreatedPayload from workos.types.organizations.organization_common import OrganizationCommon from workos.types.organizations.organization_domain import OrganizationDomain -from workos.types.roles.role import Role +from workos.types.roles.role import EventRole from workos.types.sso.connection import Connection from workos.types.user_management.email_verification import ( EmailVerificationCommon, @@ -210,15 +209,15 @@ class PasswordResetCreatedEvent(EventModel[PasswordResetCommon]): event: Literal["password_reset.created"] -class RoleCreatedEvent(EventModel[Role]): +class RoleCreatedEvent(EventModel[EventRole]): event: Literal["role.created"] -class RoleDeletedEvent(EventModel[Role]): +class RoleDeletedEvent(EventModel[EventRole]): event: Literal["role.deleted"] -class RoleUpdatedEvent(EventModel[Role]): +class RoleUpdatedEvent(EventModel[EventRole]): event: Literal["role.updated"] diff --git a/workos/types/events/event_model.py b/workos/types/events/event_model.py index e55df017..60612bd0 100644 --- a/workos/types/events/event_model.py +++ b/workos/types/events/event_model.py @@ -38,7 +38,7 @@ from workos.types.events.session_created_payload import SessionCreatedPayload from workos.types.organizations.organization_common import OrganizationCommon from workos.types.organizations.organization_domain import OrganizationDomain -from workos.types.roles.role import Role +from workos.types.roles.role import EventRole from workos.types.sso.connection import Connection from workos.types.user_management.email_verification import ( EmailVerificationCommon, @@ -72,6 +72,7 @@ DirectoryUserWithPreviousAttributes, DirectoryGroupMembershipPayload, EmailVerificationCommon, + EventRole, InvitationCommon, MagicAuthCommon, OrganizationCommon, @@ -79,7 +80,6 @@ OrganizationDomainVerificationFailedPayload, OrganizationMembership, PasswordResetCommon, - Role, SessionCreatedPayload, User, ) diff --git a/workos/types/roles/role.py b/workos/types/roles/role.py index f6023f13..7e299bd6 100644 --- a/workos/types/roles/role.py +++ b/workos/types/roles/role.py @@ -9,12 +9,11 @@ class RoleCommon(WorkOSModel): slug: str -# TODO: This is used for events/webhooks only. Rename to EventRole or something similar. -class Role(RoleCommon): +class EventRole(RoleCommon): permissions: Optional[Sequence[str]] = None -class OrganizationRole(RoleCommon): +class Role(RoleCommon): id: str name: str description: Optional[str] = None @@ -23,6 +22,6 @@ class OrganizationRole(RoleCommon): updated_at: str -class RolesList(WorkOSModel): +class RoleList(WorkOSModel): object: Literal["list"] - data: Sequence[OrganizationRole] + data: Sequence[Role] diff --git a/workos/types/webhooks/webhook.py b/workos/types/webhooks/webhook.py index 8d30a603..a9ba03f2 100644 --- a/workos/types/webhooks/webhook.py +++ b/workos/types/webhooks/webhook.py @@ -39,7 +39,7 @@ from workos.types.events.session_created_payload import SessionCreatedPayload from workos.types.organizations.organization_common import OrganizationCommon from workos.types.organizations.organization_domain import OrganizationDomain -from workos.types.roles.role import Role +from workos.types.roles.role import EventRole from workos.types.sso.connection import Connection from workos.types.user_management.email_verification import ( EmailVerificationCommon, @@ -213,15 +213,15 @@ class PasswordResetCreatedWebhook(WebhookModel[PasswordResetCommon]): event: Literal["password_reset.created"] -class RoleCreatedWebhook(WebhookModel[Role]): +class RoleCreatedWebhook(WebhookModel[EventRole]): event: Literal["role.created"] -class RoleDeletedWebhook(WebhookModel[Role]): +class RoleDeletedWebhook(WebhookModel[EventRole]): event: Literal["role.deleted"] -class RoleUpdatedWebhook(WebhookModel[Role]): +class RoleUpdatedWebhook(WebhookModel[EventRole]): event: Literal["role.updated"]