from fastapi import FastAPI
from typing import List
from pydantic import BaseModel
app = FastAPI()
# Subscriptions
class SubscriptionCreate(BaseModel):
type: str = None
topic: str = None
class Subscription(SubscriptionCreate):
id: int = None
class Config:
orm_mode = True
# Create a post
@app.post("/subscriptions", response_model=List[Subscription])
def create_subscription(data: List[SubscriptionCreate]):
print(data)
return [5]
[
{
"type": "email",
"topic": "books"
},
{
"type": "email",
"topic": "animals"
}
]
No error and response is this:
[
{
"type": null,
"topic": null,
"id": null
}
]
The error below is what I expect and I do get that if I remove class Config: orm_mode = True from the model.
pydantic.error_wrappers.ValidationError: 1 validation error for Subscription
response -> 0
value is not a valid dict (type=type_error.dict)
You are declaring all the attributes in your models with = None, so, they are not required. Then {} is a valid object for the models, without orm_mode. Then, if you return an object that is not a dict (e.g. [5]) and set orm_mode = True, Pydantic will try to access the attributes by the names (e.g. [5].id), then it finds that object you are returning doesn't have those attributes and then uses the default of None that you declared, the = None.
By setting orm_mode = True you are telling Pydantic exactly that "it's OK if I pass a non-dict value, just get the data from object attributes".
Thanks for the explanation, didn't realize it only happens when all fields have default values. This still seems like a bug to me.
"it's OK if I pass a non-dict value, just get the data from object attributes"
I would think orm_mode implies the object can additionally be an ORM model, not any arbitrary Python object.
Pydantic will try to access the attributes by the names (e.g. [5].id), then it finds that object you are returning doesn't have those attributes and then uses the default of None that you declared, the = None.
But 5.id and 5['id'] raise exceptions, they don't return None. I think the better behavior would be to only allow dicts and ORM models, or at least disallow objects that throw errors when the attributes are accessed. But I guess regardless of your opinion this is probably a pydantic issue not fastapi
Thanks for the explanation, didn't realize it only happens when all fields have default values. This still seems like a bug to me.
Hmm, I'm not sure I get what you mean.
I would think orm_mode implies the object can additionally be an ORM model, not any arbitrary Python object.
orm_mode is the name of the setting, because it has to have some name :shrug: . But it doesn't mean that it does something specific inferred by the config name.
It accepts arbitrary class instances. To know exactly how it works, check the docs in Pydantic, on the section "ORM Mode (aka Arbitrary Class Instances)": https://pydantic-docs.helpmanual.io/usage/models/#orm-mode-aka-arbitrary-class-instances
But 5.id and 5['id'] raise exceptions, they don't return None. I think the better behavior would be to only allow dicts and ORM models, or at least disallow objects that throw errors when the attributes are accessed. But I guess regardless of your opinion this is probably a pydantic issue not fastapi
Pydantic actually uses getattr, so it doesn't raise the attribute doesn't exist.
If you need to have some custom behavior in how your Pydantic models with orm_mode work, for example adding additional logic to check if you passed valid objects to them or you accidentally passed a list of int, you can use a GetterDict (explained in those same docs).
Hmm, I'm not sure I get what you mean.
I just mean we define response models to ensure the response has some specific form, and expect FastAPI to throw an error if it does not. In this case, having orm_mode=True breaks this.
In my first example, returning [5] throws an error as I would expect because 5 does not match the SubscriptionCreate model that I defined for the response. Then when I add orm_flag=True it no longer throws an error when I return [5]. That seems like a bug to me, or if you don't want to use the word bug I'd say it's unexpected behavior that should be changed to be more consistent.
If you need to have some custom behavior in how your Pydantic models with orm_mode work, for example adding additional logic to check if you passed valid objects to them or you accidentally passed a list of int, you can use a GetterDict (explained in those same docs).
It seems like this logic already exists because my first example throws the validation error saying 5 is not a dict, I just think this should still happen when orm_mode=True.
So, FastAPI doesn't really ensure the response has some specific form, that's not what the response_model really does.
It uses the response_model to convert the data to that shape doing its "best-effort". This is what allows you to return an ORM model for a User that could contain a hashed password, and then if the response_model doesn't include it, it will be filtered out, instead of raising an error.
And orm_mode is just a way to tell Pydantic to be more permissive and try even harder. This is because ORM models don't work like dicts, their data can't be extracted as would be from a dict. And even more, they don't even behave like normal classes, if you do dir(some_model) it will probably _not_ contain the actual fields of the model. Because data is actually stored somewhere else, and access is done with getters and setters. And the specifics of this depend on each ORM. So, Pydantic just does its best to extract the data using getattr which is the closest possible to the way ORMs expect to be used.
In fact, Samuel Colvin doesn't even like ORMs, he implemented orm_mode to allow those use cases and to simplify that for us, that lets us just return an ORM object (or list of ORM objects, etc). It is there only to try and serialize that data skipping private info and doing it with the best effort to save you from writing some extra lines everywhere converting your models to serialized data.
But it is not really made to ensure your code is correct. That's not its purpose. The fact that, if you return invalid data, it throws an error is more like a side effect of it. But if you enable orm_mode that just tells it "accept even weirder data from arbitrary ORM-like objects".
If you want to have orm_mode while still having more strict validations, the way to go would be to implement a custom GetterDict made exactly for your custom ORM, detecting if data is there or not, and detecting when it is an error that there's no data and when not.
The other option you have would be to serialize your models by hand and then make sure you are returning JSONable dicts and lists. But that's probably not very fun.
Hmm okay thanks for the explanation! I think it would be nice if an exception was thrown when returning an object totally unrelated to dicts or ORM models like my example... but that's probably super low priority and not easy since this only slips through with no exception when all fields are default None and I can't think of a good way to differentiate between ORM models and any other object if it's just calling getattr, unless you check it against a predefined list of supported ORM libraries