String "NaN" is coerced to float value when saving data objects (which breaks retrieving the objects) #6330
-
First Check
Commit to Help
Example Codefrom typing import List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class DataModel(BaseModel):
a_string_value: str
a_float_value: float
stored_objects: List[DataModel] = []
@app.get("/", response_model=List[DataModel])
async def get_objects():
return stored_objects
@app.post("/")
async def save_object(to_save: DataModel):
return stored_objects.append(to_save)DescriptionI have an application where one of the models contains a field that is a float value. A user once tried to send a "NaN" float value and my application accepted it and stored it in mongodb. Example calls to show the problematic behavior when NaN values are accepted: ValueError: Out of range float values are not JSON compliant I could add a validation on each of my models that use a float value or create an extension of a float that I then use in my models but this still feels like a risky way of fixing the issue. This was cleared by @tiangolo for security issues. Operating SystemLinux Operating System DetailsUbuntu 20 FastAPI Version0.73.0 Python Version3.9.10 Additional ContextThis issue was first reported as a security issue but was cleared. |
Beta Was this translation helpful? Give feedback.
Replies: 9 comments 2 replies
-
|
My goal is not to accept NaN values but to block the user from sending these to make sure the get api would always work. |
Beta Was this translation helpful? Give feedback.
-
|
Still occurs in fastapi 0.74.0 |
Beta Was this translation helpful? Give feedback.
-
import math
from typing import List
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class RegularFloat(float):
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v):
if not math.isfinite(float(v)):
raise ValueError("Invalid float.")
return cls(v)
class DataModel(BaseModel):
a_string_value: str
a_float_value: RegularFloat
stored_objects: List[DataModel] = []
@app.get("/", response_model=List[DataModel])
async def get_objects():
return stored_objects
@app.post("/")
async def save_object(to_save: DataModel):
return stored_objects.append(to_save)
if __name__ == '__main__':
uvicorn.run(
"main:app",
workers=1
)Example code used to fix my application. |
Beta Was this translation helpful? Give feedback.
-
|
Note that math.isfinite was used to also catch "Inf", "-Inf" and similar values. |
Beta Was this translation helpful? Give feedback.
-
|
So how about using Decimal instead of float |
Beta Was this translation helpful? Give feedback.
-
|
Decimal and StrictFloat are indeed also working. I will change all floats on all my models to Decimal to assure that the objects can be returned. Thank you for the help. |
Beta Was this translation helpful? Give feedback.
-
|
Thanks for the report @samv-yazzoom! And thanks for the help @yinziyan1206. I see how this behavior feels unexpected, it does to me. I made a PR to Pydantic adding support for more restrictive I would like for Let's see what happens with that PR to Pydantic. |
Beta Was this translation helpful? Give feedback.
-
|
Issue remains in latest fastapi version. If a user follows the tutorial regarding request bodies and simply adds a storage for items, their application is in my opinion vulnerable. from typing import Union, List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
stored_items: List[Item] = []
@app.get("/items")
async def get_items():
return stored_items
@app.post("/items/")
async def create_item(item: Item):
stored_items.append(item)
return itemAfter any user posts "price": "NaN", the get api is broken. Documentation could also be adjusted to hint the user to allow_inf_nan, which does resolve the issue if added by the developer. class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float = Field(allow_inf_nan=False)
tax: Union[float, None] = None |
Beta Was this translation helpful? Give feedback.
-
|
So, the solution is to use class DataModel(BaseModel):
a_string_value: str
a_float_value: float = Field(allow_inf_nan=False)or in class DataModel(BaseModel):
model_config = ConfigDict(allow_inf_nan=False)
a_string_value: str
a_float_value: floatAgree that it would be nice to add a Note in docs about this |
Beta Was this translation helpful? Give feedback.
So, the solution is to use
allow_inf_nan=Falsein etherField(alsoQuery,Form, ...)or in
model_config:Agree that it would be nice to add a Note in docs about this