HTTP API for ComfyUI workflow submission with: - POST /api/webhook endpoint for workflow submission - Real-time progress events via callback URL - Automatic output collection (no re-encoding) - Input/Output nodes for webhook data access
199 lines
5.0 KiB
Python
199 lines
5.0 KiB
Python
"""
|
|
Webhook context registry for managing active webhook requests.
|
|
Provides thread-safe storage keyed by prompt_id.
|
|
"""
|
|
|
|
import threading
|
|
import logging
|
|
from typing import Dict, Optional
|
|
from datetime import datetime, timedelta
|
|
|
|
from .types import WebhookContext
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Thread-safe registry keyed by prompt_id
|
|
_lock = threading.RLock()
|
|
_contexts: Dict[str, WebhookContext] = {}
|
|
|
|
|
|
def register_webhook_context(context: WebhookContext) -> None:
|
|
"""
|
|
Register a webhook context for tracking.
|
|
|
|
Args:
|
|
context: The WebhookContext to register
|
|
"""
|
|
with _lock:
|
|
if context.prompt_id:
|
|
_contexts[context.prompt_id] = context
|
|
context.start_time = datetime.now()
|
|
logger.debug(
|
|
f"Registered webhook context: prompt={context.prompt_id}, "
|
|
f"request={context.request_id}"
|
|
)
|
|
else:
|
|
logger.warning("Cannot register context without prompt_id")
|
|
|
|
|
|
def get_webhook_context(prompt_id: str) -> Optional[WebhookContext]:
|
|
"""
|
|
Get a context by prompt ID.
|
|
|
|
Args:
|
|
prompt_id: The ComfyUI prompt ID
|
|
|
|
Returns:
|
|
The WebhookContext if found, None otherwise
|
|
"""
|
|
with _lock:
|
|
return _contexts.get(prompt_id)
|
|
|
|
|
|
def unregister_webhook_context(prompt_id: str) -> Optional[WebhookContext]:
|
|
"""
|
|
Remove and return a context from the registry.
|
|
|
|
Args:
|
|
prompt_id: The prompt ID to unregister
|
|
|
|
Returns:
|
|
The removed WebhookContext if found, None otherwise
|
|
"""
|
|
with _lock:
|
|
context = _contexts.pop(prompt_id, None)
|
|
if context:
|
|
logger.debug(f"Unregistered webhook context: {prompt_id}")
|
|
return context
|
|
|
|
|
|
def get_all_contexts() -> Dict[str, WebhookContext]:
|
|
"""
|
|
Get a copy of all registered contexts.
|
|
Useful for debugging and monitoring.
|
|
|
|
Returns:
|
|
Dictionary mapping prompt_id to WebhookContext
|
|
"""
|
|
with _lock:
|
|
return dict(_contexts)
|
|
|
|
|
|
def clear_all_contexts() -> int:
|
|
"""
|
|
Clear all registered contexts.
|
|
Useful for testing and cleanup.
|
|
|
|
Returns:
|
|
Number of contexts that were cleared
|
|
"""
|
|
with _lock:
|
|
count = len(_contexts)
|
|
_contexts.clear()
|
|
logger.debug(f"Cleared {count} webhook contexts")
|
|
return count
|
|
|
|
|
|
def cleanup_old_contexts(max_age_hours: int = 24) -> int:
|
|
"""
|
|
Remove old completed contexts.
|
|
|
|
Args:
|
|
max_age_hours: Remove contexts older than this
|
|
|
|
Returns:
|
|
Number of contexts removed
|
|
"""
|
|
cutoff = datetime.now() - timedelta(hours=max_age_hours)
|
|
count = 0
|
|
|
|
with _lock:
|
|
to_remove = []
|
|
|
|
for prompt_id, context in _contexts.items():
|
|
# Clean up old contexts based on completed_at or start_time
|
|
if context.completed_at and context.completed_at < cutoff:
|
|
to_remove.append(prompt_id)
|
|
elif context.start_time and context.start_time < cutoff:
|
|
to_remove.append(prompt_id)
|
|
|
|
for prompt_id in to_remove:
|
|
_contexts.pop(prompt_id, None)
|
|
count += 1
|
|
|
|
if count > 0:
|
|
logger.info(f"Cleaned up {count} old webhook contexts")
|
|
|
|
return count
|
|
|
|
|
|
# Legacy function aliases for compatibility
|
|
def register_context(context: WebhookContext) -> None:
|
|
"""Legacy alias for register_webhook_context."""
|
|
register_webhook_context(context)
|
|
|
|
|
|
def get_context(request_id: str) -> Optional[WebhookContext]:
|
|
"""
|
|
Get a context by request ID (searches all contexts).
|
|
|
|
Args:
|
|
request_id: The request ID to look up
|
|
|
|
Returns:
|
|
The WebhookContext if found, None otherwise
|
|
"""
|
|
with _lock:
|
|
for context in _contexts.values():
|
|
if context.request_id == request_id:
|
|
return context
|
|
return None
|
|
|
|
|
|
def get_context_by_prompt(prompt_id: str) -> Optional[WebhookContext]:
|
|
"""Legacy alias for get_webhook_context."""
|
|
return get_webhook_context(prompt_id)
|
|
|
|
|
|
def update_context(request_id: str, **kwargs) -> bool:
|
|
"""
|
|
Update fields on a context by request ID.
|
|
|
|
Args:
|
|
request_id: The request ID to update
|
|
**kwargs: Fields to update
|
|
|
|
Returns:
|
|
True if context was found and updated
|
|
"""
|
|
with _lock:
|
|
for context in _contexts.values():
|
|
if context.request_id == request_id:
|
|
for key, value in kwargs.items():
|
|
if hasattr(context, key):
|
|
setattr(context, key, value)
|
|
return True
|
|
return False
|
|
|
|
|
|
def unregister_context(request_id: str) -> Optional[WebhookContext]:
|
|
"""
|
|
Remove a context by request ID.
|
|
|
|
Args:
|
|
request_id: The request ID to unregister
|
|
|
|
Returns:
|
|
The removed WebhookContext if found, None otherwise
|
|
"""
|
|
with _lock:
|
|
to_remove = None
|
|
for prompt_id, context in _contexts.items():
|
|
if context.request_id == request_id:
|
|
to_remove = prompt_id
|
|
break
|
|
|
|
if to_remove:
|
|
return _contexts.pop(to_remove, None)
|
|
return None
|