Pydantic Models
Schemas: Zod / interfaces → Pydantic BaseModel
หัวข้อที่มีชื่อว่า “Schemas: Zod / interfaces → Pydantic BaseModel”ใน TypeScript คุณอธิบายรูปร่าง request body ด้วย interface หรือ Zod schema ใน FastAPI คุณอธิบายด้วย subclass ของ Pydantic BaseModel ส่ง class นั้นเป็น type hint บน argument ของ route function และ FastAPI จะ parse และ validate request body อัตโนมัติ
// TypeScript — Zod schema + inferred typeimport { z } from 'zod';
const CreateItemSchema = z.object({ name: z.string().min(1), price: z.number().positive(), inStock: z.boolean().default(true), tags: z.array(z.string()).default([]),});
type CreateItem = z.infer<typeof CreateItemSchema>;
// NestJS DTO with class-validatorexport class CreateItemDto { @IsString() @MinLength(1) name: string; @IsNumber() @IsPositive() price: number; @IsBoolean() @IsOptional() inStock?: boolean = true;}# FastAPI — Pydantic BaseModelfrom pydantic import BaseModel
class CreateItem(BaseModel): name: str price: float in_stock: bool = True tags: list[str] = []
# ใช้ใน route — FastAPI parse + validate อัตโนมัติfrom fastapi import FastAPIapp = FastAPI()
@app.post("/items", status_code=201)async def create_item(item: CreateItem): return item # Pydantic serialize กลับเป็น JSONNested models
หัวข้อที่มีชื่อว่า “Nested models”Pydantic models รวมกันได้อย่างเป็นธรรมชาติ ฝัง model หนึ่งไว้ใน model อื่นเหมือน nested Zod objects
// Zod nested objectconst AddressSchema = z.object({ street: z.string(), city: z.string(), zip: z.string().length(5),});
const UserSchema = z.object({ name: z.string(), address: AddressSchema,});# Pydantic nested modelsfrom pydantic import BaseModel
class Address(BaseModel): street: str city: str zip_code: str
class User(BaseModel): name: str address: Address
# FastAPI จะ parse nested JSON นี้อัตโนมัติ:# { "name": "Alice", "address": { "street": "1 Main St", ... } }Response models — แยก input จาก output
หัวข้อที่มีชื่อว่า “Response models — แยก input จาก output”รูปแบบที่พบบ่อยคือมี model แยกกันสำหรับ input (สิ่งที่ client ส่งมา) และ output (สิ่งที่คุณส่งกลับ) Pydantic ทำให้ compose ง่ายมาก
// TypeScript — แยก input / output typesinterface CreateItemDto { name: string; price: number; }interface ItemResponse { id: number; name: string; price: number; createdAt: string; }# Pydantic — แยก input / output modelsfrom pydantic import BaseModelfrom datetime import datetime, UTC
class CreateItem(BaseModel): # Request body name: str price: float
class ItemResponse(BaseModel): # Response shape id: int name: str price: float created_at: datetime
# Route ใช้ response_model= เพื่อ filter + serializefrom fastapi import FastAPIapp = FastAPI()
@app.post("/items", response_model=ItemResponse, status_code=201)async def create_item(item: CreateItem): # ปกติจะบันทึกลง DB แล้ว return row return ItemResponse(id=1, name=item.name, price=item.price, created_at=datetime.now(UTC))Forward reference: ไม่ต้องใส่ quote อีกแล้ว
หัวข้อที่มีชื่อว่า “Forward reference: ไม่ต้องใส่ quote อีกแล้ว”ใน TypeScript type หนึ่งอ้างถึง type ที่ประกาศทีหลังในไฟล์เดียวกันได้ เพราะ compiler resolve type หลัง parse เสร็จ ส่วน Python เดิมประเมิน annotation ทันที model ที่อ้างถึงตัวเองหรืออ้างถึงชื่อที่ยังไม่ถูกประกาศจึงต้องเขียนเป็น string แล้ว Pydantic ต้องเรียก model_rebuild() ตามทีหลัง
Python 3.14 ประเมิน annotation แบบ lazy (PEP 649/749) การอ้างถึงชื่อที่นิยามทีหลังใน module เดียวกันจึงใช้ได้เลย และไม่ต้องพึ่ง from __future__ import annotations เพื่อเรื่องนี้อีก
// TypeScript — ลำดับการประกาศไม่เคยมีผลinterface Comment { text: string; replies: Comment[]; // อ้างถึงตัวเอง}
interface Order { customer: Customer; // ประกาศอยู่ข้างล่าง}
interface Customer { name: string;}# Python 3.14 — ไม่ต้อง quote ไม่ต้อง __future__ importfrom pydantic import BaseModel
class Comment(BaseModel): text: str replies: list[Comment] = [] # อ้างถึงตัวเอง: ใช้ได้บน 3.14
class Order(BaseModel): customer: Customer # นิยามอยู่ข้างล่าง: ใช้ได้เช่นกัน
class Customer(BaseModel): name: str
# ก่อน 3.14 ต้องเขียน annotation เป็น string:# replies: list["Comment"] = []# customer: "Customer"# และบางครั้งต้องเรียก Order.model_rebuild() ตามทีหลังถ้าคุณต้องอ่าน annotation เองแทนที่จะปล่อยให้ Pydantic จัดการ module annotationlib (ใหม่ใน 3.14 เช่นกัน) ให้คุณดึง annotation ออกมาได้หลายรูปแบบ รวมถึงรูปแบบ string สำหรับเครื่องมือที่ต้องไม่ทำให้เกิดการประเมินค่า
ลองเล่น — stdlib dataclasses เป็น runnable stand-in
หัวข้อที่มีชื่อว่า “ลองเล่น — stdlib dataclasses เป็น runnable stand-in”Pydantic ไม่สามารถรันใน browser ได้ dataclasses จาก standard library ให้รูปร่างเดียวกัน (typed fields, defaults, auto __init__) โดยไม่มี validation เพื่อให้สำรวจแนวคิดได้:
from dataclasses import dataclass, field, asdictimport json
@dataclassclass CreateItem: name: str price: float in_stock: bool = True tags: list[str] = field(default_factory=list) description: str | None = None
# Instantiate — same feel as BaseModel(...)item = CreateItem(name="Widget", price=9.99, tags=["sale", "new"])print("item:", item)
# Serialize to dict (like .model_dump())data = asdict(item)print("dict :", data)
# Serialize to JSONprint("json :", json.dumps(data, indent=2))
# Nested dataclass@dataclassclass OrderLine: item: CreateItem quantity: int
line = OrderLine(item=item, quantity=3)print("\nnested:", asdict(line))Loading Python runtime (first run only)…