From aa575eccf4448e9d32b1f0d767fc7cbd9afcdf72 Mon Sep 17 00:00:00 2001 From: mattgd Date: Fri, 20 Feb 2026 10:14:40 -0500 Subject: [PATCH 1/3] Fix outdated organization_membership events Default custom_attributes to empty dict and coerce null values from API to empty dict, ensuring type safety without requiring optional fields. Add event test for null custom_attributes deserialization. Co-Authored-By: Claude Haiku 4.5 --- .../organization_membership.py | 11 +++- tests/test_events.py | 51 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/workos/types/user_management/organization_membership.py b/src/workos/types/user_management/organization_membership.py index 5c7bda0f..8bbaf304 100644 --- a/src/workos/types/user_management/organization_membership.py +++ b/src/workos/types/user_management/organization_membership.py @@ -1,4 +1,6 @@ from typing import Any, Literal, Mapping, Optional, Sequence + +from pydantic import field_validator from typing_extensions import TypedDict from workos.types.workos_model import WorkOSModel @@ -21,6 +23,13 @@ class OrganizationMembership(WorkOSModel): role: OrganizationMembershipRole roles: Optional[Sequence[OrganizationMembershipRole]] = None status: LiteralOrUntyped[OrganizationMembershipStatus] - custom_attributes: Mapping[str, Any] + custom_attributes: Mapping[str, Any] = {} created_at: str updated_at: str + + @field_validator("custom_attributes", mode="before") + @classmethod + def _coerce_null_custom_attributes(cls, v: Any) -> Any: + if v is None: + return {} + return v diff --git a/tests/test_events.py b/tests/test_events.py index 6bc003c3..72ba4a2a 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -4,6 +4,7 @@ from tests.utils.fixtures.mock_event import MockEvent from tests.utils.syncify import syncify from workos.events import AsyncEvents, Events, EventsListResource +from workos.types.events import OrganizationMembershipCreatedEvent @pytest.mark.sync_and_async(Events, AsyncEvents) @@ -20,6 +21,34 @@ def mock_events(self): }, } + @pytest.fixture + def mock_organization_membership_event_with_null_custom_attributes(self): + return { + "object": "list", + "data": [ + { + "object": "event", + "id": "event_01234", + "event": "organization_membership.created", + "data": { + "object": "organization_membership", + "id": "om_01234", + "user_id": "user_01234", + "organization_id": "org_01234", + "role": {"slug": "member"}, + "status": "active", + "custom_attributes": None, + "created_at": "2024-01-01T00:00:00.000Z", + "updated_at": "2024-01-01T00:00:00.000Z", + }, + "created_at": "2024-01-01T00:00:00.000Z", + } + ], + "list_metadata": { + "after": None, + }, + } + def test_list_events( self, module_instance: Union[Events, AsyncEvents], @@ -40,3 +69,25 @@ def test_list_events( assert request_kwargs["method"] == "get" assert request_kwargs["params"] == {"events": ["dsync.activated"], "limit": 10} assert events.dict() == mock_events + + def test_list_events_organization_membership_null_custom_attributes( + self, + module_instance: Union[Events, AsyncEvents], + mock_organization_membership_event_with_null_custom_attributes, + capture_and_mock_http_client_request, + ): + capture_and_mock_http_client_request( + http_client=module_instance._http_client, + status_code=200, + response_dict=mock_organization_membership_event_with_null_custom_attributes, + ) + + events: EventsListResource = syncify( + module_instance.list_events( + events=["organization_membership.created"] + ) + ) + + event = events.data[0] + assert isinstance(event, OrganizationMembershipCreatedEvent) + assert event.data.custom_attributes == {} From dc2e04a8604f34f08aecc3baf3cf200e2ade997b Mon Sep 17 00:00:00 2001 From: mattgd Date: Fri, 20 Feb 2026 10:21:10 -0500 Subject: [PATCH 2/3] Remove null coercion; only handle undefined custom_attributes custom_attributes is never null from the API, only undefined. Remove the field_validator and null test, keep only the default and the missing-field test. Co-Authored-By: Claude Opus 4.6 --- .../organization_membership.py | 8 --- tests/test_events.py | 58 +++++++++---------- 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/src/workos/types/user_management/organization_membership.py b/src/workos/types/user_management/organization_membership.py index 8bbaf304..67ba1f21 100644 --- a/src/workos/types/user_management/organization_membership.py +++ b/src/workos/types/user_management/organization_membership.py @@ -1,6 +1,5 @@ from typing import Any, Literal, Mapping, Optional, Sequence -from pydantic import field_validator from typing_extensions import TypedDict from workos.types.workos_model import WorkOSModel @@ -26,10 +25,3 @@ class OrganizationMembership(WorkOSModel): custom_attributes: Mapping[str, Any] = {} created_at: str updated_at: str - - @field_validator("custom_attributes", mode="before") - @classmethod - def _coerce_null_custom_attributes(cls, v: Any) -> Any: - if v is None: - return {} - return v diff --git a/tests/test_events.py b/tests/test_events.py index 72ba4a2a..bfb0fff3 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -21,34 +21,6 @@ def mock_events(self): }, } - @pytest.fixture - def mock_organization_membership_event_with_null_custom_attributes(self): - return { - "object": "list", - "data": [ - { - "object": "event", - "id": "event_01234", - "event": "organization_membership.created", - "data": { - "object": "organization_membership", - "id": "om_01234", - "user_id": "user_01234", - "organization_id": "org_01234", - "role": {"slug": "member"}, - "status": "active", - "custom_attributes": None, - "created_at": "2024-01-01T00:00:00.000Z", - "updated_at": "2024-01-01T00:00:00.000Z", - }, - "created_at": "2024-01-01T00:00:00.000Z", - } - ], - "list_metadata": { - "after": None, - }, - } - def test_list_events( self, module_instance: Union[Events, AsyncEvents], @@ -70,16 +42,40 @@ def test_list_events( assert request_kwargs["params"] == {"events": ["dsync.activated"], "limit": 10} assert events.dict() == mock_events - def test_list_events_organization_membership_null_custom_attributes( + def test_list_events_organization_membership_missing_custom_attributes( self, module_instance: Union[Events, AsyncEvents], - mock_organization_membership_event_with_null_custom_attributes, capture_and_mock_http_client_request, ): + mock_response = { + "object": "list", + "data": [ + { + "object": "event", + "id": "event_01234", + "event": "organization_membership.created", + "data": { + "object": "organization_membership", + "id": "om_01234", + "user_id": "user_01234", + "organization_id": "org_01234", + "role": {"slug": "member"}, + "status": "active", + "created_at": "2024-01-01T00:00:00.000Z", + "updated_at": "2024-01-01T00:00:00.000Z", + }, + "created_at": "2024-01-01T00:00:00.000Z", + } + ], + "list_metadata": { + "after": None, + }, + } + capture_and_mock_http_client_request( http_client=module_instance._http_client, status_code=200, - response_dict=mock_organization_membership_event_with_null_custom_attributes, + response_dict=mock_response, ) events: EventsListResource = syncify( From 1cc1ed913c42d94afbf78b206ab01d6a393ba698 Mon Sep 17 00:00:00 2001 From: mattgd Date: Fri, 20 Feb 2026 10:23:43 -0500 Subject: [PATCH 3/3] Format test_events.py with ruff Co-Authored-By: Claude Opus 4.6 --- tests/test_events.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_events.py b/tests/test_events.py index bfb0fff3..d516d506 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -79,9 +79,7 @@ def test_list_events_organization_membership_missing_custom_attributes( ) events: EventsListResource = syncify( - module_instance.list_events( - events=["organization_membership.created"] - ) + module_instance.list_events(events=["organization_membership.created"]) ) event = events.data[0]