Files
2026-07-13 12:38:34 +08:00

192 lines
5.7 KiB
Python

"""
Webhook event types for typed event handling.
This module provides TypedDict definitions for specific webhook event types,
enabling type-safe handling of events like connection expiration.
"""
from __future__ import annotations
import typing as t
from enum import Enum
import typing_extensions as te
class WebhookEventType(str, Enum):
"""Known webhook event types."""
CONNECTION_EXPIRED = "composio.connected_account.expired"
TRIGGER_MESSAGE = "composio.trigger.message"
class ConnectionStatusEnum(str, Enum):
"""Connection status values."""
INITIALIZING = "INITIALIZING"
INITIATED = "INITIATED"
ACTIVE = "ACTIVE"
FAILED = "FAILED"
EXPIRED = "EXPIRED"
INACTIVE = "INACTIVE"
REVOKED = "REVOKED"
# =============================================================================
# CONNECTED ACCOUNT DATA (matches GET /api/v3/connected_accounts/{id})
# =============================================================================
class ConnectedAccountToolkit(te.TypedDict):
"""Toolkit information in connected account response."""
slug: str
class ConnectedAccountAuthConfigDeprecated(te.TypedDict, total=False):
"""Deprecated auth config fields."""
uuid: str
class ConnectedAccountAuthConfig(te.TypedDict, total=False):
"""Auth config details in connected account response."""
id: te.Required[str]
auth_scheme: te.Required[str]
is_composio_managed: te.Required[bool]
is_disabled: te.Required[bool]
deprecated: ConnectedAccountAuthConfigDeprecated
class ConnectionStateVal(te.TypedDict, total=False):
"""Connection state value - varies by auth scheme."""
status: str
# OAuth2 fields
access_token: str
refresh_token: t.Optional[str]
token_type: str
expires_in: t.Union[int, str, None]
scope: t.Union[str, t.List[str], None]
id_token: str
code_verifier: str
callback_url: str
# OAuth1 fields
oauth_token: str
oauth_token_secret: str
# API Key fields
api_key: str
generic_api_key: str
# Bearer Token fields
token: str
# Basic auth fields
username: str
password: str
class ConnectionState(te.TypedDict):
"""Connection state data discriminated by auth scheme."""
authScheme: str
val: ConnectionStateVal
class ConnectedAccountDeprecated(te.TypedDict, total=False):
"""Deprecated connected account fields."""
labels: t.List[str]
uuid: str
class SingleConnectedAccountDetailedResponse(te.TypedDict, total=False):
"""
Connected account data matching GET /api/v3/connected_accounts/{id} response.
This is used in webhook payloads for connection lifecycle events.
It intentionally does NOT reuse composio_client's ConnectedAccountRetrieveResponse
because webhook payloads arrive in raw snake_case format, while the SDK client
transforms responses to a different shape. This TypedDict validates the raw
webhook payload directly without any transformation layer.
See Also:
composio_client.types.connected_account_retrieve_response for the SDK version.
"""
toolkit: te.Required[ConnectedAccountToolkit]
auth_config: te.Required[ConnectedAccountAuthConfig]
id: te.Required[str]
user_id: te.Required[str]
status: te.Required[str] # ConnectionStatusEnum value
created_at: te.Required[str]
updated_at: te.Required[str]
state: te.Required[ConnectionState]
data: te.Required[t.Dict[str, t.Any]]
params: te.Required[t.Dict[str, t.Any]]
status_reason: te.Required[t.Optional[str]]
is_disabled: te.Required[bool]
test_request_endpoint: str
deprecated: ConnectedAccountDeprecated
# =============================================================================
# CONNECTION EXPIRED WEBHOOK EVENT
# =============================================================================
class WebhookConnectionMetadata(te.TypedDict):
"""Webhook metadata for connection events."""
project_id: str
org_id: str
class ConnectionExpiredEvent(te.TypedDict):
"""
Connection expired webhook event payload.
Emitted when a connected account expires due to authentication refresh failure.
Example:
>>> from composio.core.models.webhook_events import is_connection_expired_event
>>>
>>> def handle_webhook(payload: dict) -> None:
... if is_connection_expired_event(payload):
... print(f"Connection {payload['data']['id']} expired")
... print(f"Toolkit: {payload['data']['toolkit']['slug']}")
... print(f"User: {payload['data']['user_id']}")
"""
id: str # Unique message ID (e.g., "msg_847cdfcd-d219-4f18-a6dd-91acd42ca94a")
timestamp: str # ISO-8601 timestamp
type: t.Literal["composio.connected_account.expired"]
data: SingleConnectedAccountDetailedResponse
metadata: WebhookConnectionMetadata
# Type alias for non-trigger webhook events with specific typed schemas.
# Trigger events (composio.trigger.message) are handled through the
# TriggerEvent type in triggers.py via the trigger subscription system.
WebhookEvent = t.Union[ConnectionExpiredEvent]
def is_connection_expired_event(
payload: t.Dict[str, t.Any],
) -> t.TypeGuard[ConnectionExpiredEvent]:
"""
Check if a webhook payload is a connection expired event.
:param payload: The webhook payload to check
:return: True if this is a connection expired event
Example:
>>> from composio.core.models.webhook_events import is_connection_expired_event
>>>
>>> if is_connection_expired_event(payload):
... handle_connection_expired(payload) # payload is narrowed to ConnectionExpiredEvent
"""
return (
isinstance(payload, dict)
and payload.get("type") == WebhookEventType.CONNECTION_EXPIRED.value
)