Async vs Sync Hooks

Intermediate Hook Patterns

DVP CMS is a truth distillation system for AI-generated content. Plugins are evidence suppliers—they verify facts, pull live data, and make content more trustworthy over time. Learn more →

When to use async hooks and how to implement them correctly.

Overview

DVP CMS hooks can be either synchronous or asynchronous. The choice depends on:

Rule of Thumb:

Decision Matrix

ScenarioUse SyncUse Async
Validate input data
Transform content
Calculate values
Check permissions (local)
HTTP API calls
Database queries
File I/O
Send notifications
Deploy to CDN

Sync Hook Implementation

from typing import TYPE_CHECKING, Any
from dvp_cms.plugins.base import Plugin
from dvp_cms.plugins.hookspec import ContentLifecycleHooks

if TYPE_CHECKING:
    from dvp_cms.plugins.context import HookContext


class ValidationPlugin(Plugin, ContentLifecycleHooks):
    """Plugin with sync hooks for validation."""

    name = "validation"
    version = "1.0.0"

    def dvp_before_content_create(
        self,
        ctx: "HookContext",
        content: dict[str, Any],
        metadata: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """Sync hook - quick validation and transformation."""
        # CPU-bound operations are fine
        if not content.get("title"):
            content["title"] = "Untitled"

        # Add computed fields
        content["word_count"] = len(content.get("body", "").split())

        return content

Async Hook Implementation

class NotificationPlugin(Plugin, ContentLifecycleHooks):
    """Plugin with async hooks for I/O operations."""

    name = "notification"
    version = "1.0.0"

    async def dvp_content_published(
        self,
        ctx: "HookContext",
        content_id: str,
        content: dict[str, Any],
        metadata: dict[str, Any] | None = None,
    ) -> None:
        """Async hook - sends notifications over network."""
        import httpx

        # Network I/O - must be async
        async with httpx.AsyncClient() as client:
            await client.post(
                self._webhook_url,
                json={"text": f"Content published: {content.get('title')}"},
            )

Common Mistakes

Blocking in Async Context

# WRONG - blocking call in async hook
async def dvp_content_published(self, ctx, content_id, content, metadata=None):
    import requests  # Blocking library!
    requests.post(url, data=content)  # Blocks event loop!

# RIGHT - use async HTTP client
async def dvp_content_published(self, ctx, content_id, content, metadata=None):
    import httpx
    async with httpx.AsyncClient() as client:
        await client.post(url, json=content)

Forgetting to Await

# WRONG - missing await
async def dvp_content_published(self, ctx, content_id, content, metadata=None):
    self._send_notification(content_id)  # Returns coroutine, doesn't execute!

# RIGHT - await the coroutine
async def dvp_content_published(self, ctx, content_id, content, metadata=None):
    await self._send_notification(content_id)

Performance Patterns

Parallel Async Operations

async def dvp_content_published(self, ctx, content_id, content, metadata=None):
    import asyncio

    # Run independent operations in parallel
    await asyncio.gather(
        self._notify_slack(content_id),
        self._update_search_index(content_id),
        self._log_analytics(content_id),
    )

Timeout Protection

async def dvp_execute_health_check(self, check, ctx, execution_ctx):
    import asyncio

    try:
        result = await asyncio.wait_for(
            self._check_domain(execution_ctx.domain),
            timeout=check.timeout_seconds,
        )
        return HealthCheckResult(
            domain=execution_ctx.domain,
            overall_status=HealthStatus.HEALTHY,
        )
    except asyncio.TimeoutError:
        return HealthCheckResult(
            domain=execution_ctx.domain,
            overall_status=HealthStatus.UNHEALTHY,
            details=[HealthCheckDetail(message="Check timed out")],
        )

See Also