Step 5 - Schemas & Dynamic Security (Zchema)
Define Pydantic schemas and use Zchema to automatically mask sensitive fields based on user context.
Instead of writing multiple Pydantic models for different user roles (e.g., TaskResponsePublic, TaskResponseAdmin), ZCore provides role-aware field projection through Zchema.
Define Schemas
Open tasks/schemas.py and define your creation, update, and response schemas:
# tasks/schemas.py
import uuid
from zcore import Zchema
from pydantic import ConfigDict
class TaskCreate(Zchema):
__model__ = "tasks" # Binds this schema to the Task domain
title: str
is_completed: bool = False
assignee_email: str | None = None
class TaskUpdate(Zchema):
__model__ = "tasks"
title: str | None = None
is_completed: bool | None = None
assignee_email: str | None = None
class TaskResponse(Zchema):
__model__ = "tasks"
__private__ = {"assignee_email"} # Automatically hidden for unauthenticated guests
id: uuid.UUID
title: str
is_completed: bool
assignee_email: str | None = None
model_config = ConfigDict(from_attributes=True)Eliminating Role-Based Schema Duplication
Zchema eliminates the need to maintain duplicate schemas for different permission levels (such as TaskCreatePublic vs TaskCreateAdmin or TaskResponseUser vs TaskResponseAdmin). By dynamically sanitizing inputs (preventing Mass Assignment) and pruning response fields based on ctx.restricted_fields, a single schema definition per operation handles all authorization levels automatically.
How Zchema Works
Context Shielding: By setting __model__ = "tasks", Zchema is now aware of its domain.
During the request lifecycle, if the active user lacks certain permissions, their restricted fields are loaded into ctx.restricted_fields (e.g., "tasks.view.assignee_email").
When TaskResponse serializes the data, Zchema intercepts the process and automatically prunes the assignee_email from the JSON output. No if statements required.
Guest-Level Masking (__private__): In addition to role-based pruning via ctx.restricted_fields, you can declare __private__ = {"assignee_email"} on your Zchema. These fields are automatically pruned for unauthenticated guest requests (ctx.user_id is None) across responses, inputs, and OpenAPI schemas.
We now have secure data transfer objects. Let's build the data access layer.