mirror of
https://github.com/langgenius/dify.git
synced 2026-11-11 05:41:09 +08:00
113 lines
4.2 KiB
Python
113 lines
4.2 KiB
Python
"""Dispatch persisted trigger endpoints without holding a subscription transaction."""
|
|
|
|
import logging
|
|
import secrets
|
|
import time
|
|
from collections.abc import Callable, Mapping
|
|
from dataclasses import dataclass
|
|
from typing import Any, Protocol
|
|
|
|
from flask import Request, Response
|
|
|
|
from core.plugin.entities.request import TriggerDispatchResponse
|
|
from services.trigger.subscription_service import TriggerSubscriptionSnapshot
|
|
from services.workflow.entities import PluginTriggerDispatchData
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TriggerEndpointLoadError(Exception):
|
|
"""A persisted subscription or its provider configuration could not be loaded."""
|
|
|
|
|
|
class TriggerEndpointProviderNotFoundError(Exception):
|
|
"""The subscription's plugin provider is no longer available."""
|
|
|
|
|
|
class TriggerEndpointEventNotFoundError(Exception):
|
|
"""The plugin dispatched an event absent from its provider declaration."""
|
|
|
|
|
|
class TriggerEndpointQuery(Protocol):
|
|
def get_by_endpoint(self, *, endpoint_id: str) -> TriggerSubscriptionSnapshot | None:
|
|
"""Return the capability's detached owner/configuration, or None when absent."""
|
|
...
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class TriggerEndpointDispatch:
|
|
result: TriggerDispatchResponse
|
|
event_names: frozenset[str]
|
|
|
|
|
|
class TriggerEndpointGateway(Protocol):
|
|
def dispatch(self, *, subscription: TriggerSubscriptionSnapshot, request: Request) -> TriggerEndpointDispatch: ...
|
|
|
|
|
|
class TriggerEndpointRequestStore(Protocol):
|
|
def persist_request(self, request_id: str, request: Request) -> None: ...
|
|
|
|
def persist_payload(self, request_id: str, payload: Mapping[str, Any]) -> None: ...
|
|
|
|
|
|
class TriggerEndpointService:
|
|
def __init__(
|
|
self,
|
|
*,
|
|
subscriptions: TriggerEndpointQuery,
|
|
provider: TriggerEndpointGateway,
|
|
requests: TriggerEndpointRequestStore,
|
|
publish: Callable[[PluginTriggerDispatchData], None],
|
|
) -> None:
|
|
self._subscriptions = subscriptions
|
|
self._provider = provider
|
|
self._requests = requests
|
|
self._publish = publish
|
|
|
|
def process(self, endpoint_id: str, request: Request) -> Response | None:
|
|
"""Return the plugin response, or None so the caller can try a temporary builder."""
|
|
timestamp = int(time.time())
|
|
try:
|
|
subscription = self._subscriptions.get_by_endpoint(endpoint_id=endpoint_id)
|
|
except Exception as error:
|
|
raise TriggerEndpointLoadError(f"Failed to load trigger endpoint {endpoint_id}.") from error
|
|
if subscription is None:
|
|
return None
|
|
|
|
dispatched = self._provider.dispatch(subscription=subscription, request=request)
|
|
result = dispatched.result
|
|
if result.events:
|
|
request_id = f"trigger_request_{timestamp}_{secrets.token_hex(6)}"
|
|
self._requests.persist_request(request_id, request)
|
|
self._requests.persist_payload(request_id, result.payload)
|
|
for event_name in result.events:
|
|
if event_name not in dispatched.event_names:
|
|
logger.error(
|
|
"Event name %s not found in provider %s for endpoint %s",
|
|
event_name,
|
|
subscription.provider_id,
|
|
endpoint_id,
|
|
)
|
|
raise TriggerEndpointEventNotFoundError(
|
|
f"Event name {event_name} not found in provider {subscription.provider_id}"
|
|
)
|
|
self._publish(
|
|
PluginTriggerDispatchData(
|
|
user_id=result.user_id,
|
|
tenant_id=subscription.tenant_id,
|
|
endpoint_id=subscription.endpoint_id,
|
|
provider_id=subscription.provider_id,
|
|
subscription_id=subscription.id,
|
|
timestamp=timestamp,
|
|
events=list(result.events),
|
|
request_id=request_id,
|
|
)
|
|
)
|
|
logger.info(
|
|
"Queued async dispatching for %d triggers on endpoint %s with request_id %s",
|
|
len(result.events),
|
|
endpoint_id,
|
|
request_id,
|
|
)
|
|
return result.response
|