Skip to content

fix: restore typed EventSchema discriminated union dispatcher#629

Merged
gjtorikian merged 5 commits intomainfrom
reintroduce-types
Apr 22, 2026
Merged

fix: restore typed EventSchema discriminated union dispatcher#629
gjtorikian merged 5 commits intomainfrom
reintroduce-types

Conversation

@gjtorikian
Copy link
Copy Markdown
Contributor

@gjtorikian gjtorikian commented Apr 22, 2026

Summary

A user reported that the previous typed events in v5 were lost on the major upgrade. This PR restores them.

  • EventSchema is now a dispatcher class rather than a flat dataclass with data: Dict[str, Any]
  • EventSchema.from_dict() reads the event field and routes to the correct typed variant (e.g. DsyncUserCreated, UserCreated)
  • Added EventSchemaVariant = Union[ActionAuthenticationDenied, ..., VaultNamesListed] covering all 95 event types
  • events.list_events() returns SyncPage[EventSchemaVariant] / AsyncPage[EventSchemaVariant]
  • webhooks.verify_event() and _verification.verify_event() return EventSchemaVariant
  • Round-trip tests for EventSchema removed (dispatcher has no to_dict()); webhook verification fixture updated with a complete User payload

Test plan

  • uv run nox -s ci passes (1908 tests, lint, type check, format)
  • workos.events.list_events() — each item is a fully-typed variant (e.g. DsyncUserCreated with data: DirectoryUser)
  • workos.webhooks.verify_event() — returns the correct variant for the event type in the payload
  • Unknown event types return EventSchemaUnknown with the raw payload preserved. Missing or None discriminator raises WorkOSError.

🤖 Generated with Claude Code

EventSchema.from_dict() now dispatches to the correct typed variant
(e.g. DsyncUserCreated, UserCreated) based on the 'event' field,
instead of returning a flat dataclass with data: Dict[str, Any].

list_events() and verify_event() return SyncPage[EventSchemaVariant] /
AsyncPage[EventSchemaVariant] and EventSchemaVariant respectively,
where EventSchemaVariant = Union[ActionAuthenticationDenied, ...] covers
all 95 concrete event types with fully-typed data fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gjtorikian gjtorikian requested review from a team as code owners April 22, 2026 16:40
@gjtorikian gjtorikian requested a review from dandorman April 22, 2026 16:40
@gjtorikian gjtorikian changed the title feat: typed EventSchema discriminated union dispatcher fix: typed EventSchema discriminated union dispatcher Apr 22, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR restores typed event dispatching that was lost in the v5 upgrade. EventSchema is converted from a flat dataclass to a pure dispatcher whose from_dict() reads the event discriminator and routes to one of 95 typed variants (or EventSchemaUnknown for unrecognized types). All public API points — list_events(), verify_event() — now return EventSchemaVariant. The implementation is clean and the core logic is correct.

Confidence Score: 5/5

This PR is safe to merge; no P0 or P1 issues found.

All findings are P2 or lower. The dispatcher logic is correct: _raise_deserialize_error is NoReturn so there are no implicit None returns; each variant model's from_dict receives the full event dict as expected; unknown events gracefully fall back to EventSchemaUnknown; tests cover dispatch, unknown fallback, and error cases.

No files require special attention.

Important Files Changed

Filename Overview
src/workos/events/models/event_schema.py Core dispatcher: EventSchema converted to a routing class; EventSchemaUnknown and EventSchemaVariant union added. Logic is correct — _raise_deserialize_error is NoReturn, dispatch fallthrough to unknown is safe.
src/workos/webhooks/_verification.py Return type updated to EventSchemaVariant; EventSchemaVariant is only imported under TYPE_CHECKING (safe due to from future import annotations), consistent with docstring update.
src/workos/events/_resource.py cast() wrapping used to bridge EventSchema dispatcher (which pagination calls from_dict on) to EventSchemaVariant return type; type: ignore[arg-type] is correctly scoped.
tests/test_webhook_verification.py Fixture updated with full User payload required by UserCreated.from_dict; assertions switched from EventSchema to UserCreated; all verification paths covered.
tests/test_models_round_trip.py Round-trip tests for EventSchema (which no longer has to_dict()) removed; replaced by TestDiscriminatorDispatch covering known dispatch, unknown fallback, and error cases.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Raw event dict / webhook payload] --> B{EventSchema.from_dict}
    B --> C{"'event' key present?"}
    C -- No --> D[_raise_deserialize_error\nMissing required field]
    C -- Yes --> E{"event value is None?"}
    E -- Yes --> F[_raise_deserialize_error\nevent must not be None]
    E -- No --> G{_DISPATCH.get\ndiscriminator value}
    G -- Known key --> H[dispatch_cls.from_dict\ne.g. UserCreated, DsyncUserCreated]
    G -- Unknown key --> I[EventSchemaUnknown.from_dict\nraw_data preserved]
    H --> J[EventSchemaVariant]
    I --> J
    J --> K[list_events → SyncPage / AsyncPage]
    J --> L[verify_event → EventSchemaVariant]
Loading

Reviews (5): Last reviewed commit: "fix: tighten event dispatch types and te..." | Re-trigger Greptile

Comment thread src/workos/events/models/event_schema.py Outdated
gjtorikian and others added 3 commits April 22, 2026 13:25
…rom_dict

Raises a distinct "Missing required field 'event'" error when the key is absent,
rather than falling through to the ambiguous "Unknown event: None" message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gjtorikian
Copy link
Copy Markdown
Contributor Author

@greptile review

@gjtorikian gjtorikian changed the title fix: typed EventSchema discriminated union dispatcher fix: restore typed EventSchema discriminated union dispatcher Apr 22, 2026
The `_DISPATCH` dict was typed as `Dict[str, Any]`, losing
type safety on the class values. `EventSchemaVariant` was
eagerly imported at runtime in `_verification.py` despite
only being needed for type-checking annotations. Event list
tests only checked page wrapper types without verifying that
deserialization produces concrete event subclasses.
@gjtorikian gjtorikian changed the title fix: restore typed EventSchema discriminated union dispatcher fix: restore typed EventSchema discriminated union dispatcher Apr 22, 2026
@gjtorikian gjtorikian merged commit af95901 into main Apr 22, 2026
11 checks passed
@gjtorikian gjtorikian deleted the reintroduce-types branch April 22, 2026 22:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant