New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Treat NaN as None/null #1779
Comments
Hello @vkolotov from math import isnan
from pydantic import BaseModel as PydanticBaseModel, validator
class BaseModel(PydanticBaseModel):
@validator('*')
def change_nan_to_none(cls, v, field):
if field.outer_type_ is float and isnan(v):
return None
return v
class Model(BaseModel):
a: float
b: float
class Model2(BaseModel):
m: Model
n: float
model = Model(a=float('nan'), b=3.1)
print(repr(model))
print(repr(Model2(m=model, n=float('nan')))) Model(a=None, b=3.1)
Model2(m=Model(a=None, b=3.1), n=None) |
I think there is an error in the signature of change_nan_to_none, which will cause an error. The correct signature should be:
|
According to the docs, the original method signature is correct, and your additions are also correct, but optional:
|
How would I make the provided solution work with JSON serialization? It is possible to create the models, but reading the model from JSON results in the issue that there none in the data
import json
from pathlib import Path
with tempfile.TemporaryDirectory() as tmp_dir:
json_path = Path(tmp_dir) / "example.json"
with open(json_path, "w") as f_json:
f_json.write(model.json())
with open(json_path, "r") as f_json:
d = json.load(f_json)
model2 = Model(**d) I tried to change the model to `a=Optional[float], but then I get the error:
How would I change the model to work with NaNs also for JSON? NaN/Inf/-Inf values are very common in scientific contexts. |
It seems like the root issue is the inability of Pydantic to refuse NaN without a field specific validation. I think it would be useful to have a specific constrained type for floats that accepts all floats except NaN. |
I think this workaround will be easier in V2 because you can create a "constrained type" much easier and integrate it with type checkers, IDEs, etc. from math import isnan
from typing import Annotated, Any, Optional, TypeVar
from pydantic import BaseModel
from pydantic.annotated_arguments import BeforeValidator
def coerce_nan_to_none(x: Any) -> Any:
if isnan(x):
return None
return x
T = TypeVar('T')
NoneOrNan = Annotated[Optional[T], BeforeValidator(coerce_nan_to_none)]
class Model(BaseModel):
a: NoneOrNan[float]
b: NoneOrNan[float]
m = Model(a=float('nan'), b=3.1)
assert m.a is None
assert m.b == 3.1 |
Feature Request
Output of
python -c "import pydantic.utils; print(pydantic.utils.version_info())"
:On our project we have to deal with pandas. Pandas treats "non existing values" (nulls) as NaN (which is weird in the first instance, nevertheless we have to live with that). We use pydantic to validate data that is generated by pandas, in order to do this we convert pandas DataFrame to dictionary (or list of dictionaries) and then convert dictionaries to pydantic dataclasses. The resulted dataclasses are serialised into json (then stored in DB or in Elasticsearch).
The problem here is, DataFrame's may contain NaN values, they get transferred to pydantic dataclasses without any error (as NaN is an instance of float), then those NaNs get serialised into json as nulls (which is not correct, they are not optional).
We would like to catch this "nullability" issue as a part of the normal pydantic validation, e.g. if a float field that is not Optional gets assigned with NaN, we would like to get a validation error (e.g. None is not allowed value).
Providing custom validators via
@validator
or via__get_validators__
is not an option as we don't want to specify all these for each dataclass. What would be great if we could:pydantic.validators.float_validator
and make it global/default.validators.float_validator
correctly handles NaNs.There is another solution that we currently use - defining custom type for float, but this requires using this type in each dataclass. It is a viable solution, but still not great.
The text was updated successfully, but these errors were encountered: