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.
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.