ZCore LogoZCore
How to

How to use pre/post hooks in Services

Execute business logic, compute fields, coordinate side-effects, and handle soft-delete restoration across mutation lifecycles.

ZCore's BaseService divides database operations into three structured layers: Pre-hooks (intercept/merge payload), On-methods (core repository execution), and Post-hooks (side-effects, external notifications & cleanup).

1. Using pre_* and post_* Hooks

Return a dict from any pre_* mutation hook to automatically merge that data into the database payload:

# services.py
from typing import Any
from zcore import BaseService
from zcore.utils.helpers import slugify
from .models import Task
from .schemas import TaskCreate, TaskUpdate
from .repositories import TaskRepo

class TaskService(BaseService[Task]):
    def __init__(self, repo: TaskRepo):
        super().__init__(model=Task, repository=repo)
    
    async def pre_create(self, schema: TaskCreate) -> dict[str, Any] | None:
        # Merges automatically into the insert payload
        return {"slug": slugify(schema.title)}

    async def post_delete(self, model: Task, force: bool = False) -> None:
        # Executes after deletion (e.g., delete files from storage if hard-deleted)
        if force and getattr(model, "file_path", None):
            # await storage.delete(model.file_path)
            pass

    async def post_restore(self, model: Task) -> None:
        # Executes after a soft-deleted task is successfully restored
        # logger.info(f"Task {model.id} restored")
        pass

2. Overriding Core Execution (on_* Methods)

If you need to change how an entity is persisted in the repository (e.g., routing to a custom repository method or decorating execution), override the on_* methods directly:

class TaskService(BaseService[Task]):
    def __init__(self, repo: TaskRepo):
        super().__init__(model=Task, repository=repo)

    async def on_create(self, schema: TaskCreate, **extra_data) -> Task:
        # Custom persistence delegation (surrounded safely by pre_create and post_create)
        return await self.repository.create(schema, **extra_data)
        
    async def on_delete(self, target: Task | Any, force: bool = False) -> Task | None:
        # Core delete delegation (delegates soft vs hard delete to repository)
        return await self.repository.delete(target, force=force)

3. Complete Lifecycle Hook Reference

HookCategoryReturn TypePurpose / Description
post_get(model)Read (Single)ModelModify or append computed runtime fields after fetching.
post_get_multi(models)Read (Batch)Sequence[Model]Intercept and process a batch list of retrieved entities.
pre_create(schema)Create (Single)dict | NoneCompute/merge data (e.g., slugs, hashes) before insert.
post_create(model)Create (Single)NoneTrigger post-insert events (e.g., send emails, webhooks).
pre_create_multi(schemas)Create (Batch)NonePre-validation check before inserting multiple records.
post_create_multi(models)Create (Batch)NoneRun notifications or logging after bulk insertions.
pre_update(target, schema, partial)Update (Single)dict | NoneCompute/merge data into the update payload.
post_update(model)Update (Single)NoneClear cache keys or log modification audit trails.
pre_update_multi(data, partial)Update (Batch)NoneIntercept batch dictionary updates before execution.
post_update_multi(models)Update (Batch)NonePerform side-effects after bulk updates finish.
pre_delete(id, force=False)Delete (Single)NoneVerify dependencies or cascade rules before row removal.
post_delete(model, force=False)Delete (Single)NonePurge associated files from storage after deletion.
pre_delete_multi(ids, force=False)Delete (Batch)NoneValidate or check permissions for a list of target IDs.
post_delete_multi(models, force=False)Delete (Batch)NoneBatch cleanup of external resources after deletions.
pre_restore(id)Restore (Single)NonePre-validation checks before restoring a soft-deleted record.
post_restore(model)Restore (Single)NoneTrigger side-effects or logging after a record is restored.
pre_restore_multi(ids)Restore (Batch)NoneValidate or check permissions before restoring batch records.
post_restore_multi(models)Restore (Batch)NoneRun notifications or audit logs after batch restorations.
pre_search(search_in)SearchNoneInspect or mutate SearchRequest filters dynamically.
post_search(models)SearchNonePost-process or enrich dynamic search query results.

Atomic Transaction Safety: All pre_*, on_*, and post_* operations execute within the active transaction before _safe_commit() is invoked. If any hook raises an error, the entire operation is automatically rolled back.

On this page