FastAPI’s type annotations, while a boon for developer experience and correctness, can become a surprising bottleneck in your application’s hottest code paths.
Let’s see it in action. Imagine a simple FastAPI endpoint that processes a large list of items, each with a few fields.
from fastapi import FastAPI, Body
from pydantic import BaseModel
from typing import List
app = FastAPI()
class Item(BaseModel):
id: int
name: str
value: float
@app.post("/process_items/")
async def process_items(items: List[Item] = Body(...)):
# Simulate some processing
processed_count = 0
for item in items:
# In a real app, this might be a database lookup or complex calculation
processed_count += item.id * item.value
return {"processed_count": processed_count, "item_count": len(items)}
When a request hits this endpoint, FastAPI and Pydantic perform several steps:
- Request Body Parsing: The raw JSON request body is received.
- Type Validation: Pydantic iterates through the
itemslist. For each dictionary in the list, it creates anItemmodel instance. This involves:- Checking if
idis an integer,nameis a string, andvalueis a float. - Performing any custom validation defined on the
Itemmodel. - Coercing types where possible (e.g., "123" to
123for an integer field).
- Checking if
- Model Instantiation: A
List[Item]object is constructed. - Business Logic: Your
process_itemsfunction then iterates over these validatedItemmodel instances.
The validation and instantiation of Pydantic models, especially within loops for lists, can add significant overhead. For every single item in the request, Pydantic is doing work: checking keys, validating types, and creating Python objects. If you’re processing thousands of items, this adds up.
The core problem is that Pydantic’s default behavior is to be thoroughly validating and always creating model instances. This is fantastic for API correctness and general use, but for a high-throughput endpoint where you know the data is already well-formed (e.g., from a trusted internal service or a well-tested frontend), this validation becomes redundant work.
The primary lever you have to control this is Pydantic’s Config and model_config (for Pydantic v2+). Specifically, you can disable validation for certain fields or even the entire model if you’re confident in the input.
For Pydantic v1.x, you’d add a Config class:
from fastapi import FastAPI, Body
from pydantic import BaseModel
from typing import List
app = FastAPI()
class Item(BaseModel):
id: int
name: str
value: float
class Config:
# This setting disables all validation and model instantiation
# for all fields in this model.
# Use with extreme caution.
orm_mode = True # or arbitrary_types_allowed = True if you want to bypass certain checks
For Pydantic v2.x+, you use model_config:
from fastapi import FastAPI, Body
from pydantic import BaseModel, ConfigDict
from typing import List
app = FastAPI()
class Item(BaseModel):
id: int
name: str
value: float
# For Pydantic v2.x+
model_config = ConfigDict(
# This setting disables all validation and model instantiation.
# Use with extreme caution.
validate_assignment=False,
arbitrary_types_allowed=True # Often used in conjunction to bypass certain checks
)
Setting orm_mode = True (v1) or validate_assignment=False and arbitrary_types_allowed=True (v2) effectively tells Pydantic to trust the input data structure and types, skipping the expensive validation and instantiation steps for each item. FastAPI will still receive the raw data, but Pydantic won’t try to "fix" or validate it when it’s passed to your endpoint function.
The most surprising thing most people miss is that orm_mode = True (v1) or arbitrary_types_allowed=True (v2) doesn’t just allow ORM objects; it significantly bypasses Pydantic’s core data validation and parsing pipeline. If you use this on a BaseModel that represents your request body, Pydantic will simply pass the raw dictionary or list of dictionaries directly to your endpoint function without performing any checks or creating model instances. This means your endpoint function will receive List[dict] instead of List[Item], and you’ll need to handle that difference.
If you’re using Pydantic v2 and need to disable validation for a specific endpoint’s request body without disabling it for the model globally, you can use Depends with a custom callable that bypasses Pydantic validation.
The next problem you’ll encounter is that after bypassing Pydantic validation, you’ve lost the safety net. You’ll need to implement manual checks or ensure your data source is extremely reliable, or risk runtime errors from malformed data.