pydantic-model
georgekhananaev/claude-skills-vault/.claude/skills/pydantic-model/SKILL.md
Pydantic v2 model patterns for req/res validation, MongoDB conversion, validation rules. Travel Panel conventions.
Skill28 starsChanged 4 months ago
What's in it
- Pydantic Model Skill
- When to Use
- Project Context
- CRITICAL: v2 API Only
- Model Creation
- Step 1: Create Model File
- Step 2: Export from Package
- Model Patterns
- Create Request
- Update Request (Partial)
- Response w/ MongoDB Conversion
- Nested Models
- Enum Validation
- Field Validators
- Model Validators
- Generic Responses
- MongoDB Doc Prep
- Field Constraints
- Model Config
- Checklist
---
name: pydantic-model
description: Pydantic v2 model patterns for req/res validation, MongoDB conversion, validation rules. Travel Panel conventions.
---
# Pydantic Model Skill
Pydantic v2 model guidance for Travel Panel.
## When to Use
- Creating req/res models for endpoints
- Defining DTOs
- Adding validation rules
- MongoDB ↔ API response conversion
## Project Context
- Models: `app/classes/<feature>/`
- Version: Pydantic v2 only
- Docs: `docs/endpoint-development-guide.md`
## CRITICAL: v2 API Only
| Deprecated (v1) | Use (v2) |
|------------------|-----------------------|
| `__fields__` | `model_fields` |
| `__validators__` | `model_validators` |
| `schema()` | `model_json_schema()` |
| `parse_obj()` | `model_validate()` |
| `dict()` | `model_dump()` |
| `json()` | `model_dump_json()` |
## Model Creation
### Step 1: Create Model File
Location: `app/classes/<feature>/<feature>_models.py`
```python
from pydantic import BaseModel, Field, field_validator, model_validator
from typing import Optional, List
from datetime import datetime, timezone
from enum import Enum
class StatusEnum(str, Enum):
ACTIVE = "active"
INACTIVE = "inactive"
PENDING = "pending"
class ItemCreate(BaseModel):
"""Create item request."""
name: str = Field(..., min_length=1, max_length=255, examples=["My Item"])
description: Optional[str] = Field(None, max_length=2000)
status: StatusEnum = Field(default=StatusEnum.ACTIVE)
tags: List[str] = Field(default_factory=list, max_length=10)
price: float = Field(..., gt=0)
@field_validator("name")
@classmethod
def validate_name(cls, v: str) -> str:
v = v.strip()
if not v:
raise ValueError("Name cannot be empty")
return v
@field_validator("tags")
@classmethod
def validate_tags(cls, v: List[str]) -> List[str]:
return list(set(tag.lower().strip() for tag in v if tag.strip()))
class ItemUpdate(BaseModel):
"""Update item request (all opt)."""
name: Optional[str] = Field(None, min_length=1, max_length=255)
description: Optional[str] = Field(None, max_length=2000)
status: Optional[StatusEnum] = None
tags: Optional[List[str]] = None
price: Optional[float] = Field(None, gt=0)
@model_validator(mode="after")
def check_at_least_one_field(self) -> "ItemUpdate":
if not self.model_dump(exclude_unset=True):
raise ValueError("At least one field must be provided")
return self
class ItemGet(BaseModel):
"""Item response."""
id: str
name: str
description: Optional[str] = None
status: str
tags: List[str] = Field(default_factory=list)
price: float
company_id: str
created_at: datetime
updated_at: Optional[datetime] = None
created_by: Optional[str] = None
@classmethod
def from_mongo(cls, doc: dict) -> "ItemGet":
return cls(
id=str(doc.get("_id", "")),
name=doc.get("name", ""),
description=doc.get("description"),
status=doc.get("status", "active"),
tags=doc.get("tags", []),
price=doc.get("price", 0.0),
company_id=doc.get("company_id", ""),
created_at=doc.get("created_at", datetime.now(timezone.utc)),
updated_at=doc.get("updated_at"),
created_by=doc.get("created_by"),
)
class ItemListMeta(BaseModel):
totalRowCount: int
page: Optional[int] = None
pageSize: Optional[int] = None
stats: Optional[dict] = None
class ItemListResponse(BaseModel):
data: List[ItemGet]
meta: ItemListMeta
```
### Step 2: Export from Package
Location: `app/classes/<feature>/__init__.py`
```python
from .feature_models import (
ItemCreate, ItemUpdate, ItemGet,
ItemListResponse, ItemListMeta, StatusEnum,
)
__all__ = [
"ItemCreate", "ItemUpdate", "ItemGet",
"ItemListResponse", "ItemListMeta", "StatusEnum",
]
```
## Model Patterns
### Create Request
```python
class BookingCreate(BaseModel):
customer_id: str = Field(..., description="Customer ObjectId")
product_id: str = Field(..., description="Product ObjectId")
check_in: datetime
check_out: datetime
guests: int = Field(..., ge=1, le=20)
special_requests: Optional[str] = Field(None, max_length=1000)
@model_validator(mode="after")
def validate_dates(self) -> "BookingCreate":
if self.check_out <= self.check_in:
raise ValueError("check_out must be after check_in")
return self
```
### Update Request (Partial)
```python
class BookingUpdate(BaseModel):
check_in: Optional[datetime] = None
check_out: Optional[datetime] = None
guests: Optional[int] = Field(None, ge=1, le=20)
special_requests: Optional[str] = Field(None, max_length=1000)
status: Optional[str] = None
model_config = {"extra": "forbid"}
```
### Response w/ MongoDB Conversion
```python
class BookingGet(BaseModel):
id: str
customer_id: str
product_id: str
check_in: datetime
check_out: datetime
guests: int
status: str
total_price: float
created_at: datetime
customer: Optional[dict] = None
product: Optional[dict] = None
@classmethod
def from_mongo(cls, doc: dict) -> "BookingGet":
return cls(
id=str(doc["_id"]),
customer_id=str(doc.get("customer_id", "")),
product_id=str(doc.get("product_id", "")),
check_in=doc["check_in"],
check_out=doc["check_out"],
guests=doc.get("guests", 1),
status=doc.get("status", "pending"),
total_price=doc.get("total_price", 0.0),
created_at=doc.get("created_at", datetime.now(timezone.utc)),
customer=doc.get("customer"),
product=doc.get("product"),
)
```
### Nested Models
```python
class Address(BaseModel):
street: str
city: str
country: str
postal_code: Optional[str] = None
class CustomerCreate(BaseModel):
name: str
email: str
phone: Optional[str] = None
address: Optional[Address] = None
```
### Enum Validation
```python
class PaymentStatus(str, Enum):
PENDING = "pending"
PAID = "paid"
FAILED = "failed"
REFUNDED = "refunded"
class PaymentUpdate(BaseModel):
status: PaymentStatus # Auto-validated
```
### Field Validators
```python
import re
class CustomerCreate(BaseModel):
email: str
phone: Optional[str] = None
@field_validator("email")
@classmethod
def validate_email(cls, v: str) -> str:
v = v.lower().strip()
if not re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", v):
raise ValueError("Invalid email format")
return v
@field_validator("phone")
@classmethod
def validate_phone(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return None
digits = re.sub(r"\D", "", v)
if len(digits) < 10:
raise ValueError("Phone number too short")
return digits
```
### Model Validators
```python
class DateRangeFilter(BaseModel):
start_date: Optional[datetime] = None
end_date: Optional[datetime] = None
@model_validator(mode="after")
def validate_date_range(self) -> "DateRangeFilter":
if self.start_date and self.end_date:
if self.end_date < self.start_date:
raise ValueError("end_date must be >= start_date")
return self
```
### Generic Responses
```python
from typing import TypeVar, Generic
T = TypeVar("T")
class SuccessResponse(BaseModel):
success: bool = True
message: str
class CreateResponse(SuccessResponse):
_id: str
item_id: Optional[str] = None
class ListResponse(BaseModel, Generic[T]):
data: List[T]
meta: dict
```
### MongoDB Doc Prep
```python
class ItemCreate(BaseModel):
name: str
status: str = "active"
def to_mongo(self, company_id: str, user_id: str) -> dict:
now = datetime.now(timezone.utc)
return {
**self.model_dump(),
"company_id": company_id,
"created_at": now,
"updated_at": now,
"created_by": user_id,
}
```
## Field Constraints
```python
# String
name: str = Field(..., min_length=1, max_length=100)
code: str = Field(..., pattern=r"^[A-Z]{3}-\d{4}$")
# Numeric
price: float = Field(..., gt=0)
quantity: int = Field(..., ge=0, le=1000)
rating: float = Field(..., ge=0, le=5)
# List
tags: List[str] = Field(default_factory=list, max_length=10)
# Optional w/ default
status: str = Field(default="active")
notes: Optional[str] = Field(default=None, max_length=5000)
```
## Model Config
```python
class MyModel(BaseModel):
model_config = {
"extra": "forbid", # Reject unknown fields
"str_strip_whitespace": True, # Auto-strip strings
"validate_assignment": True, # Validate on attr set
"populate_by_name": True, # Allow field aliases
"json_schema_extra": {"examples": [{"name": "Example"}]},
}
```
## Checklist
- [ ] Use Pydantic v2 API only
- [ ] Separate models: Create, Update, Get
- [ ] Add `from_mongo()` for response models
- [ ] Use `Field()` for constraints & descriptions
- [ ] `@field_validator` for custom validation
- [ ] `@model_validator` for cross-field validation
- [ ] Define enums for fixed values
- [ ] Export from `__init__.py`
- [ ] Add docstrings
- [ ] Use `Optional[]` for nullable fields
- [ ] Use `datetime.now(timezone.utc)` for timestampsMore agent context in georgekhananaev/claude-skills-vault
63 other files this repository gives its agents, the first 60 shown.
AGENTS.md
Skill
- agy-cli.claude/skills/agy-cli/SKILL.md
- aws-cli.claude/skills/aws-cli/SKILL.md
- better-auth.claude/skills/better-auth/SKILL.md
- brainstorm.claude/skills/brainstorm/SKILL.md
- claude-seo.claude/skills/claude-seo/SKILL.md
- code-quality.claude/skills/code-quality/SKILL.md
- codex-cli.claude/skills/codex-cli/SKILL.md
- color-accessibility-audit.claude/skills/color-accessibility-audit/SKILL.md
- vercel-composition-patterns.claude/skills/composition-patterns/SKILL.md
- data-wrangler.claude/skills/data-wrangler/SKILL.md
- doc-navigator.claude/skills/doc-navigator/SKILL.md
- domain-checker.claude/skills/domain-checker/SKILL.md
- fastapi-senior-dev.claude/skills/fastapi-senior-dev/SKILL.md
- file-converter.claude/skills/file-converter/SKILL.md
- firebase-cli.claude/skills/firebase-cli/SKILL.md
- firecrawl.claude/skills/firecrawl-cli/SKILL.md
- frontend-design.claude/skills/frontend-design/SKILL.md
- gemini-cli.claude/skills/gemini-cli/SKILL.md
- github-cli.claude/skills/github-cli/SKILL.md
- materialreacttable-mastery.claude/skills/materialreacttable-mastery/SKILL.md
- mcp-builder.claude/skills/mcp-builder/SKILL.md
- mermaid-diagram.claude/skills/mermaid-diagram/SKILL.md
- monday-com.claude/skills/monday-com/SKILL.md
- mongodb-atlas-cli.claude/skills/mongodb-atlas-cli/SKILL.md
- multi-agent-patterns.claude/skills/multi-agent-patterns/SKILL.md
- n8n-cli.claude/skills/n8n-cli/SKILL.md
- natural-language.claude/skills/natural-language/SKILL.md
- neon-postgres-agent-platforms.claude/skills/neon-postgres-agent-platforms/SKILL.md
- next-cache-components.claude/skills/next-cache-components/SKILL.md
- nextjs-senior-dev.claude/skills/nextjs-senior-dev/SKILL.md
- next-upgrade.claude/skills/next-upgrade/SKILL.md
- notebooklm.claude/skills/notebooklm-skill/SKILL.md
- obsidian-skills.claude/skills/obsidian-skills/SKILL.md
- owasp-security.claude/skills/owasp-security/SKILL.md
- parallel-agents.claude/skills/parallel-agents/SKILL.md
- planning-with-files.claude/skills/planning-with-files/SKILL.md
- plan-to-tdd.claude/skills/plan-to-tdd/SKILL.md
- project-change-log.claude/skills/project-change-log/SKILL.md
- react-best-practices.claude/skills/react-best-practices/SKILL.md
- salesforce-cli.claude/skills/salesforce-cli/SKILL.md
- semantic-coding.claude/skills/semantic-coding/SKILL.md
- senior-backend.claude/skills/senior-backend/SKILL.md
- skill-creator.claude/skills/skill-creator/SKILL.md
- stripe-best-practices.claude/skills/stripe-best-practices/SKILL.md
- supabase-cli.claude/skills/supabase-cli/SKILL.md
- swift-concurrency-6-2.claude/skills/swift-concurrency6.2/SKILL.md
- swiftui-patterns.claude/skills/swiftui-patterns/SKILL.md
- system-architect.claude/skills/system-architect/SKILL.md
- terraform.claude/skills/terraform/SKILL.md
- testing-automation-expert.claude/skills/testing-automation-expert/SKILL.md
- test-levels.claude/skills/test-levels/SKILL.md
- token-optimizer.claude/skills/token-optimizer/SKILL.md
- trailofbits-security.claude/skills/trailofbits-security/SKILL.md
- ui-ux-pro-max.claude/skills/ui-ux-pro-max/SKILL.md
- uiux-toolkit.claude/skills/uiux-toolkit/SKILL.md
- upgrade-packages-js.claude/skills/upgrade-packages-js/SKILL.md
- vercel-cli.claude/skills/vercel-cli/SKILL.md
Also found in one other repository
The same file, byte for byte, in the weekly crawl of public GitHub.
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool public_context_discussion, action report. How to connect one.

