Day 18 · ~12m

Generic Models & TypeVar

Creating reusable model templates with Generic and TypeVar.

🧑‍💻

I keep creating the same wrapper pattern — like a response object that has data, status, and message — but the type of data changes. Is there a way to make that reusable?

👩‍🏫

Yes — generic models. Just like list[int] and list[str] are the same container with different contents, you can create models that are parameterized by a type:

from pydantic import BaseModel
from typing import TypeVar, Generic

T = TypeVar("T")

class ApiResponse(BaseModel, Generic[T]):
    data: T
    status: str
    message: str = ""

T is a placeholder. When you use the model, you fill it in:

class User(BaseModel):
    name: str
    email: str

# ApiResponse with User data
response = ApiResponse[User](
    data={"name": "Alice", "email": "alice@test.com"},
    status="ok"
)
print(type(response.data))  # <class 'User'>
print(response.data.name)    # "Alice"
🧑‍💻

So ApiResponse[User] validates the data field as a User model? And ApiResponse[str] would validate it as a string?

👩‍🏫

Exactly. The type parameter flows through to validation:

# String data
str_response = ApiResponse[str](data="hello", status="ok")
print(str_response.data)  # "hello"

# List data
list_response = ApiResponse[list[int]](data=[1, 2, 3], status="ok")
print(list_response.data)  # [1, 2, 3]

One model definition, infinite type combinations. Each version validates its data field according to the type you specify.

🧑‍💻

Can I use this for pagination? Like a paginated response where the items could be any type?

👩‍🏫

That's one of the most common uses:

class PaginatedResponse(BaseModel, Generic[T]):
    items: list[T]
    total: int
    page: int
    page_size: int

class Product(BaseModel):
    name: str
    price: float

page = PaginatedResponse[Product](
    items=[{"name": "Widget", "price": 9.99}],
    total=50,
    page=1,
    page_size=10
)
print(page.items[0].name)  # "Widget"

Every item in the list gets validated as a Product. You get type safety and reusability in one pattern.

🧑‍💻

Can I have multiple type parameters? Like a key-value pair where both the key and value types vary?

👩‍🏫

Yes. Define multiple TypeVars:

K = TypeVar("K")
V = TypeVar("V")

class KeyValuePair(BaseModel, Generic[K, V]):
    key: K
    value: V

pair = KeyValuePair[str, int](key="count", value=42)
print(pair.key)    # "count"
print(pair.value)  # 42

Generic models are the Pydantic equivalent of generic containers in typed languages. They let you write one model that works with any data type, while still getting full validation.

Practice your skills

Sign up to write and run code in this lesson.

Already have an account? Sign in