AI Reading
Quick summary of this article
This module teaches how to use Pydantic models in FastAPI to validate incoming data and structure outgoing responses. Request models ensure client data is correct and safe, while response models control what information is sent back, improving security and consistency.
- Pydantic models define data structure using Python type hints, automatically validating types and converting data (e.g., string "123" to integer 123).
- Request models group client data into a single object; FastAPI validates it automatically and returns a 422 error if data is invalid.
- Response models filter outgoing data, hiding sensitive fields like passwords and ensuring responses match a defined structure.
- Field validation includes built-in options (e.g., string length limits, integer ranges, email format) and custom validators for complex rules like password matching.
- Optional fields (using Optional[type] with None default) allow clients to omit data, perfect for partial updates, while default values provide fallbacks for missing fields.
Introduction
Welcome to Module 4 of the FastAPI Complete Course. In the previous modules, you learned how to set up FastAPI, define basic path operations, and handle path and query parameters. Now, we step into one of the most powerful features of FastAPI: Request & Response Models.
When building professional APIs, you cannot rely on raw data. You need to ensure that the data sent by clients is valid, structured, and safe. Similarly, the data you return to clients must be predictable and well-formed. This is where Pydantic comes in.
FastAPI uses Pydantic models to define the shape of your data. By the end of this chapter, you will be able to:
- Understand what Pydantic is and why it is essential for FastAPI.
- Create request models to validate incoming data.
- Define response models to control what data is sent back.
- Use field validation, default values, and optional fields effectively.
- Avoid common pitfalls that beginners face.
The primary focus keyword for this chapter is FastAPI Request & Response Models. Let’s dive deep into this topic and build a solid foundation for your API development journey.
Introduction to Pydantic
Pydantic is a Python library that provides data validation and settings management using Python type annotations. It is the backbone of FastAPI’s data handling. When you define a class that inherits from BaseModel, you are creating a Pydantic model.
Why Pydantic?
- Type Safety: Pydantic enforces type hints at runtime. If a client sends a string where an integer is expected, Pydantic raises a validation error.
- Automatic Parsing: It can convert data types automatically. For example, a string
"123"can be parsed into an integer123if the field is declared asint. - JSON Schema: Pydantic models automatically generate JSON Schema, which FastAPI uses to create interactive API documentation (Swagger UI).
- Nested Models: You can define complex data structures by nesting Pydantic models inside each other.
Installing Pydantic
If you are using FastAPI, Pydantic is already installed as a dependency. However, you can install it separately if needed:
pip install pydantic
Let’s create a simple Pydantic model to understand its basic structure.
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
email: str
In this example:
Personinherits fromBaseModel.- We define three fields:
name(string),age(integer), andemail(string). - When you create an instance of
Person, Pydantic validates the data automatically.
# Valid data
p = Person(name="Alice", age=30, email="alice@example.com")
print(p) # Output: name='Alice' age=30 email='alice@example.com'
# Invalid data (age is a string)
try:
p = Person(name="Bob", age="thirty", email="bob@example.com")
except Exception as e:
print(e) # Validation error
This is the foundation of FastAPI Request & Response Models. Now, let’s see how to use these models in a FastAPI application.
Creating Request Models
In FastAPI, a request model defines the structure of the data you expect from the client. Instead of accepting individual parameters, you can group them into a Pydantic model and use it as a parameter in your path operation.
Basic Request Model Example
Let’s create an API endpoint that accepts user registration data.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserRegistration(BaseModel):
username: str
email: str
password: str
@app.post("/register")
async def register_user(user: UserRegistration):
# Process the user data
return {"message": f"User {user.username} registered successfully!"}
Explanation:
- We define a Pydantic model
UserRegistrationwith three fields. - In the path operation function, we declare a parameter
userof typeUserRegistration. - FastAPI automatically reads the request body, validates it against the model, and passes the validated data to the function.
- If the client sends invalid data (e.g., missing fields or wrong types), FastAPI returns a 422 Unprocessable Entity error with details.
Testing with Swagger UI
Run the FastAPI application:
uvicorn main:app --reload
Open http://127.0.0.1:8000/docs in your browser. You will see the Swagger UI with the /register endpoint. Click on it, then click “Try it out”. You can send a JSON request like:
{
"username": "john_doe",
"email": "john@example.com",
"password": "securepass123"
}
The API will return a success message. This is the power of FastAPI Request & Response Models in action.
Response Models
Response models allow you to control what data is sent back to the client. You can use a Pydantic model to define the structure of the response, ensuring consistency and security (e.g., hiding sensitive fields like passwords).
Basic Response Model Example
Let’s extend the previous example to return a structured response.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserRegistration(BaseModel):
username: str
email: str
password: str
class UserResponse(BaseModel):
username: str
email: str
message: str
@app.post("/register", response_model=UserResponse)
async def register_user(user: UserRegistration):
# In a real app, you would save the user to a database
return UserResponse(
username=user.username,
email=user.email,
message="User registered successfully!"
)
Key points:
- We define a second Pydantic model
UserResponsewith the fields we want to expose. - We use the
response_modelparameter in the decorator to specify the response type. - FastAPI will automatically filter and validate the response data. If you accidentally include extra fields, they will be removed.
- Notice that the
passwordfield is not included inUserResponse, so it will never be sent to the client.
Using response_model with List
You can also return lists of models.
from typing import List
class Item(BaseModel):
name: str
price: float
@app.get("/items", response_model=List[Item])
async def get_items():
return [
Item(name="Laptop", price=999.99),
Item(name="Mouse", price=19.99)
]
This endpoint returns a JSON array of items, each conforming to the Item model.
Field Validation
Pydantic provides powerful field validation capabilities. You can use Python’s type hints, Pydantic’s built-in validators, or custom validators to enforce rules on your data.
Built-in Validators
Pydantic supports many types that automatically validate data:
constr(min_length=..., max_length=...)for stringsconint(ge=..., le=...)for integersconfloat(ge=..., le=...)for floatsEmailStrfor email validation (requirespydantic[email])
from pydantic import BaseModel, EmailStr, conint, constr
class UserProfile(BaseModel):
username: constr(min_length=3, max_length=20)
age: conint(ge=18, le=120)
email: EmailStr
# Valid data
profile = UserProfile(username="alice", age=25, email="alice@example.com")
# Invalid data (age too low)
try:
profile = UserProfile(username="bob", age=15, email="bob@example.com")
except Exception as e:
print(e) # Validation error
Explanation:
constr(min_length=3, max_length=20)ensures the username is between 3 and 20 characters.conint(ge=18, le=120)ensures age is between 18 and 120.EmailStrvalidates that the string is a valid email format.
Custom Validators
For more complex validation, you can use the @validator decorator.
from pydantic import BaseModel, validator
class PasswordCheck(BaseModel):
password: str
confirm_password: str
@validator('confirm_password')
def passwords_match(cls, v, values):
if 'password' in values and v != values['password']:
raise ValueError('Passwords do not match')
return v
# Valid data
check = PasswordCheck(password="secret123", confirm_password="secret123")
# Invalid data
try:
check = PasswordCheck(password="secret123", confirm_password="different")
except Exception as e:
print(e) # Validation error
Key points about custom validators:
- The first argument is
cls(the class). - The second argument is the value of the field being validated.
- The
valuesparameter (optional) gives access to other fields. - You must return the validated value or raise a
ValueError.
Default Values
Default values make fields optional in the sense that if the client does not provide them, the model uses the default. However, they are still considered required in the JSON Schema unless you explicitly mark them as optional.
Setting Default Values
from pydantic import BaseModel
from typing import Optional
class Product(BaseModel):
name: str
price: float
description: str = "No description provided"
in_stock: bool = True
# Client sends only name and price
product = Product(name="Widget", price=9.99)
print(product.description) # Output: No description provided
print(product.in_stock) # Output: True
In this example:
descriptiondefaults to"No description provided".in_stockdefaults toTrue.- The client can still override these values by sending them in the request.
Default Values with Factory Functions
For mutable defaults like lists or dictionaries, you must use default_factory.
from pydantic import BaseModel
from typing import List
from datetime import datetime
class Order(BaseModel):
items: List[str] = []
created_at: datetime = datetime.now()
# This is WRONG for mutable types
class WrongOrder(BaseModel):
items: List[str] = [] # This will be shared across all instances!
Instead, use:
from pydantic import Field
class CorrectOrder(BaseModel):
items: List[str] = Field(default_factory=list)
created_at: datetime = Field(default_factory=datetime.now)
Explanation:
default_factorycalls the function every time a new instance is created.- This ensures each instance gets its own list or timestamp.
Optional Fields
Optional fields allow the client to omit values entirely. In Pydantic, you use Optional[type] from the typing module, and the field’s default is None.
Defining Optional Fields
from pydantic import BaseModel
from typing import Optional
class UserUpdate(BaseModel):
username: Optional[str] = None
email: Optional[str] = None
age: Optional[int] = None
In this model:
- All fields are optional. The client can send only the fields they want to update.
- If a field is not provided, its value will be
None. - This is perfect for PATCH endpoints where you only update specific fields.
Difference Between Optional and Default
There is a subtle difference:
Optional[str] = None: The field can be omitted entirely. If omitted, it becomesNone.str = "default": The field is still required in the JSON Schema, but if omitted, it gets the default value.
If you want a field to be truly optional (not required in the schema), use Optional with None as default.
from pydantic import BaseModel
from typing import Optional
class Config(BaseModel):
debug: bool = False # Required in schema, but has default
log_level: Optional[str] = None # Not required in schema
In the Swagger UI, debug will appear as a required field (though it has a default), while log_level will be optional.
Common Mistakes
Here are frequent errors beginners make with FastAPI Request & Response Models:
- Forgetting to use response_model: If you don’t specify
response_model, FastAPI returns the raw Pydantic object, which might include sensitive fields. - Mutating default values: Using mutable default values like
[]or{}directly in the model definition. Always useField(default_factory=...). - Confusing Optional with default: Using
Optional[str]without setting a default (= None) still makes the field required in the schema. - Not using EmailStr for emails: Relying on plain
strfor email fields means no validation. UseEmailStrfrompydantic. - Over-validating: Adding too many custom validators can make code hard to read. Use built-in validators when possible.
Practice Task
Now it’s your turn. Create a FastAPI application that manages a library of books. Your task:
- Define a Pydantic model
BookRequestfor creating a book with fields:title: string, min length 1, max length 100author: string, min length 3year: integer, between 1900 and current yearisbn: optional string, if provided must be exactly 13 characters
- Define a response model
BookResponsethat includes all fields except any internal IDs (if you add one). - Create a POST endpoint
/booksthat acceptsBookRequestand returnsBookResponsewith a generated ID. - Create a GET endpoint
/books/{book_id}that returns a single book. - Use an in-memory list to store books.
Test your API using Swagger UI. Ensure that validation errors are returned for invalid data.
Summary
In this chapter, you learned the core concepts of FastAPI Request & Response Models:
- Pydantic is the data validation library that powers FastAPI’s request and response handling.
- Request models define the structure of incoming data, ensuring it is valid before your code processes it.
- Response models control what data is sent back, improving security and consistency.
- Field validation using built-in types and custom validators enforces business rules.
- Default values make fields optional while providing sensible defaults.
- Optional fields allow clients to omit data entirely, perfect for partial updates.
Mastering these concepts is crucial for building robust, production-ready APIs. You now have the tools to handle complex data scenarios with confidence.
FAQs
1. What is the difference between Pydantic and Python dataclasses?
Pydantic models provide automatic validation, JSON Schema generation, and parsing capabilities that Python dataclasses lack. While dataclasses are great for simple data containers, Pydantic is specifically designed for data validation in APIs.
2. Can I use the same model for both request and response?
Yes, but it is not recommended. Request models often contain fields like passwords that should not be exposed in responses. It is better practice to define separate models for requests and responses.
3. How do I handle nested JSON objects?
Pydantic supports nested models. You can define a model with fields that are themselves Pydantic models. FastAPI will automatically validate the nested structure.
4. What happens if validation fails?
FastAPI returns a 422 Unprocessable Entity response with a detailed error message indicating which fields failed validation and why.
5. Can I use Pydantic models with query parameters?
Yes, but you need to use Depends() with a Pydantic model to parse query parameters. However, for simple cases, individual query parameters are more straightforward.
Congratulations on completing Module 4! You now have a solid understanding of data validation in FastAPI. In Module 5, we will explore Dependency Injection—a powerful pattern for reusing code and managing shared logic across your API. Get ready to take your FastAPI skills to the next level!
Additional Practical Example
Let’s build a more comprehensive example that combines multiple concepts from this module: a user profile management system with nested models, validation, and response filtering.
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, EmailStr, Field, validator
from typing import List, Optional
from datetime import datetime
import uuid
app = FastAPI(title="User Profile API")
# --- Address Model ---
class Address(BaseModel):
street: str = Field(..., min_length=5, max_length=100)
city: str = Field(..., min_length=2, max_length=50)
zip_code: str = Field(..., regex=r"^d{5}(-d{4})?$")
country: str = Field(default="USA", max_length=50)
# --- User Profile Model ---
class UserProfile(BaseModel):
user_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
username: str = Field(..., min_length=3, max_length=20, regex=r"^[a-zA-Z0-9_]+$")
email: EmailStr
full_name: str = Field(..., min_length=2, max_length=100)
age: int = Field(..., ge=18, le=120)
is_active: bool = True
created_at: datetime = Field(default_factory=datetime.utcnow)
addresses: List[Address] = Field(default_factory=list)
@validator("username")
def validate_username_no_admin(cls, v):
if v.lower() == "admin":
raise ValueError("Username cannot be 'admin'")
return v
@validator("age")
def validate_age_range(cls, v):
if v < 18:
raise ValueError("User must be at least 18 years old")
return v
# --- Response Models ---
class UserProfileResponse(BaseModel):
user_id: str
username: str
full_name: str
is_active: bool
created_at: datetime
addresses: List[Address]
class UserProfileSummary(BaseModel):
user_id: str
username: str
is_active: bool
# --- In-memory database ---
fake_db = {}
# --- Endpoints ---
@app.post("/users/", response_model=UserProfileResponse, status_code=status.HTTP_201_CREATED)
async def create_user_profile(profile: UserProfile):
"""Create a new user profile with validation."""
if profile.username in [u.username for u in fake_db.values()]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Username already exists"
)
fake_db[profile.user_id] = profile
return profile
@app.get("/users/{user_id}", response_model=UserProfileResponse)
async def get_user_profile(user_id: str):
"""Retrieve a full user profile."""
if user_id not in fake_db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
)
return fake_db[user_id]
@app.get("/users/", response_model=List[UserProfileSummary])
async def list_users():
"""List all users with summary information."""
return [
UserProfileSummary(
user_id=u.user_id,
username=u.username,
is_active=u.is_active
)
for u in fake_db.values()
]
@app.patch("/users/{user_id}/activate", response_model=UserProfileResponse)
async def activate_user(user_id: str):
"""Activate a user profile."""
if user_id not in fake_db:
raise HTTPException(status_code=404, detail="User not found")
user = fake_db[user_id]
user.is_active = True
return user
This example demonstrates several important patterns:
- Nested models: The
Addressmodel is embedded insideUserProfileusingList[Address] - Custom validation: We use
@validatordecorators to check username uniqueness and age requirements - Field constraints:
Field()withmin_length,max_length,regex, andge/leensures data integrity - Multiple response models: We define
UserProfileResponse(full data) andUserProfileSummary(minimal data) for different endpoints - Error handling: Proper HTTP exceptions with meaningful messages
Class-Based Implementation Example
While FastAPI works well with function-based views, you can also organize your code using classes. This is particularly useful for grouping related endpoints and reusing logic. Here’s how to implement the same user profile system using a class-based approach:
from fastapi import FastAPI, HTTPException, status, Depends
from pydantic import BaseModel, EmailStr, Field, validator
from typing import List, Optional
from datetime import datetime
import uuid
app = FastAPI(title="Class-Based User API")
# --- Models (same as before) ---
class Address(BaseModel):
street: str = Field(..., min_length=5, max_length=100)
city: str = Field(..., min_length=2, max_length=50)
zip_code: str = Field(..., regex=r"^d{5}(-d{4})?$")
country: str = Field(default="USA", max_length=50)
class UserProfile(BaseModel):
user_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
username: str = Field(..., min_length=3, max_length=20, regex=r"^[a-zA-Z0-9_]+$")
email: EmailStr
full_name: str = Field(..., min_length=2, max_length=100)
age: int = Field(..., ge=18, le=120)
is_active: bool = True
created_at: datetime = Field(default_factory=datetime.utcnow)
addresses: List[Address] = Field(default_factory=list)
@validator("username")
def validate_username_no_admin(cls, v):
if v.lower() == "admin":
raise ValueError("Username cannot be 'admin'")
return v
class UserProfileResponse(BaseModel):
user_id: str
username: str
full_name: str
is_active: bool
created_at: datetime
addresses: List[Address]
class UserProfileSummary(BaseModel):
user_id: str
username: str
is_active: bool
# --- Database dependency ---
class Database:
def __init__(self):
self._users = {}
def add_user(self, user: UserProfile) -> UserProfile:
if user.username in [u.username for u in self._users.values()]:
raise HTTPException(status_code=400, detail="Username exists")
self._users[user.user_id] = user
return user
def get_user(self, user_id: str) -> UserProfile:
if user_id not in self._users:
raise HTTPException(status_code=404, detail="User not found")
return self._users[user_id]
def list_users(self) -> List[UserProfile]:
return list(self._users.values())
def activate_user(self, user_id: str) -> UserProfile:
user = self.get_user(user_id)
user.is_active = True
return user
# --- Dependency injection ---
def get_database() -> Database:
return Database()
# --- Class-based router ---
class UserRouter:
def __init__(self, db: Database = Depends(get_database)):
self.db = db
async def create_user(self, profile: UserProfile) -> UserProfileResponse:
return self.db.add_user(profile)
async def get_user(self, user_id: str) -> UserProfileResponse:
return self.db.get_user(user_id)
async def list_users(self) -> List[UserProfileSummary]:
users = self.db.list_users()
return [
UserProfileSummary(
user_id=u.user_id,
username=u.username,
is_active=u.is_active
)
for u in users
]
async def activate_user(self, user_id: str) -> UserProfileResponse:
return self.db.activate_user(user_id)
# --- Register endpoints using class methods ---
user_router = UserRouter()
@app.post("/users/", response_model=UserProfileResponse, status_code=201)
async def create_user(profile: UserProfile):
return await user_router.create_user(profile)
@app.get("/users/{user_id}", response_model=UserProfileResponse)
async def get_user(user_id: str):
return await user_router.get_user(user_id)
@app.get("/users/", response_model=List[UserProfileSummary])
async def list_users():
return await user_router.list_users()
@app.patch("/users/{user_id}/activate", response_model=UserProfileResponse)
async def activate_user(user_id: str):
return await user_router.activate_user(user_id)
The class-based approach offers several advantages:
- Code organization: Related methods are grouped together, making the codebase easier to navigate
- Reusability: You can instantiate the router with different database instances for testing
- Dependency injection: The
Databaseclass can be easily swapped with a real database connection - Separation of concerns: Business logic lives in the router class, while endpoint definitions remain clean
Hands-On Practice Task
Now it’s your turn to apply what you’ve learned. Build a book inventory management system with the following requirements:
- Create a Book model with fields:
isbn(string, must match pattern^d{3}-d{10}$)title(string, 1-200 characters)author(string, 2-100 characters)publication_year(integer, between 1900 and current year)price(float, between 0.01 and 999.99)genres(list of strings, each 3-30 characters)in_stock(boolean, default True)
- Create response models:
BookResponse(all fields except internal notes)BookSummary(only isbn, title, author, price, in_stock)
- Implement endpoints:
POST /books/– Add a new book (return full details)GET /books/{isbn}– Get a specific bookGET /books/– List all books (return summaries only)PATCH /books/{isbn}/stock– Toggle stock status
- Add validation:
- Ensure ISBN is unique
- Validate publication year is not in the future
- Ensure at least one genre is provided
Starter code:
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field, validator
from typing import List, Optional
from datetime import datetime
app = FastAPI(title="Book Inventory")
# Your code here
# 1. Define Book model with all fields and validators
# 2. Define BookResponse and BookSummary models
# 3. Create in-memory storage
# 4. Implement all endpoints
Expected output when testing:
# Test creating a book
curl -X POST "http://localhost:8000/books/"
-H "Content-Type: application/json"
-d '{
"isbn": "978-1234567890",
"title": "Python for Beginners",
"author": "John Doe",
"publication_year": 2023,
"price": 29.99,
"genres": ["Programming", "Education"]
}'
# Expected response (201 Created):
{
"isbn": "978-1234567890",
"title": "Python for Beginners",
"author": "John Doe",
"publication_year": 2023,
"price": 29.99,
"genres": ["Programming", "Education"],
"in_stock": true
}
# Test listing books
curl "http://localhost:8000/books/"
# Expected response (200 OK):
[
{
"isbn": "978-1234567890",
"title": "Python for Beginners",
"author": "John Doe",
"price": 29.99,
"in_stock": true
}
]
Hints:
- Use
Field(default_factory=datetime.utcnow)for timestamps - Use
@validatorfor custom validation logic - Store books in a dictionary keyed by ISBN
- Use
response_modelparameter to control output
Common Interview Questions
Here are typical interview questions related to request and response models in FastAPI, along with detailed answers:
Q1: What is the difference between a Pydantic model and a FastAPI response model?
Answer: A Pydantic model defines the data structure, validation, and serialization logic. A FastAPI response model (specified via the response_model parameter) controls what data is actually sent to the client. The response model can be a subset or transformation of the internal model. For example, you might have a User model with a password_hash field, but your response model UserResponse excludes that field for security. FastAPI automatically filters the data based on the response model definition.
Q2: How do you handle optional fields in request bodies?
Answer: Use Optional[type] from Python’s typing module combined with a default value. For example:
from typing import Optional
from pydantic import BaseModel
class UpdateItem(BaseModel):
name: Optional[str] = None
price: Optional[float] = None
This allows clients to send partial updates. If a field is not provided, it defaults to None. You can then check which fields are None to implement partial updates.
Q3: Explain the purpose of response_model_exclude_unset.
Answer: When set to True, FastAPI will only include fields that were explicitly set by the client in the response. This is useful for PATCH endpoints where you want to return only the fields that were actually updated. For example:
@app.patch("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def update_item(item_id: int, item: Item):
# Only return fields that were provided in the request
return item
Q4: How do you validate complex nested data structures?
Answer: Pydantic supports nested models natively. You can define models within models:
class Address(BaseModel):
street: str
city: str
class User(BaseModel):
name: str
address: Address # Nested model
class Company(BaseModel):
name: str
employees: List[User] # List of nested models
FastAPI automatically validates all levels of nesting. You can also add @validator methods to nested models for custom validation at each level.
Q5: What is the difference between Field() and validator?
Answer: Field() is used to define constraints on a single field, such as minimum length, maximum value, regex patterns, and default values. Validators (@validator) are functions that can perform complex validation logic that may involve multiple fields or external data. For example, Field(min_length=3) ensures a string has at least 3 characters, while a validator can check that a password matches a confirmation field or that a username doesn’t exist in a database.
Q6: How do you handle file uploads with Pydantic models?
Answer: File uploads in FastAPI are handled separately from Pydantic models using UploadFile and Form parameters. You cannot directly include file data in a JSON request body. Instead, you use multipart form data:
from fastapi import FastAPI, UploadFile, File, Form
from pydantic import BaseModel
class ItemMetadata(BaseModel):
name: str
description: str
@app.post("/items/")
async def create_item(
metadata: str = Form(...), # JSON string of metadata
file: UploadFile = File(...)
):
item_metadata = ItemMetadata.parse_raw(metadata)
# Process file and metadata
Q7: What happens if you don’t specify a response model?
Answer: If you don’t specify response_model, FastAPI will return the raw Pydantic model object, which gets serialized to JSON using the model’s default serialization. This means all fields will be included unless you’ve used Field(exclude=True) or other exclusion mechanisms. It’s generally good practice to always specify a response model to have explicit control over what data is exposed.
Q8: How can you make a field read-only in the response?
Answer: You can use different models for input and output. For example, the input model includes password, but the output model excludes it. Alternatively, you can use Pydantic’s Field(..., exclude=True) on the response model or use response_model_exclude parameter:
class UserInput(BaseModel):
username: str
password: str
class UserOutput(BaseModel):
username: str
# No password field
@app.post("/users/", response_model=UserOutput)
async def create_user(user: UserInput):
# Process user, return only username
return UserOutput(username=user.username)
These questions cover the most important concepts from this module. Understanding them will help you both in interviews and in building robust FastAPI applications.
