Skip to content
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

Add ConfigDict.ser_json_inf_nan #8159

Merged
merged 2 commits into from Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/concepts/json_schema.md
Expand Up @@ -187,6 +187,7 @@ Specifically, the following config options are relevant:
* [`json_schema_extra`][pydantic.config.ConfigDict.json_schema_extra]
* [`ser_json_timedelta`][pydantic.config.ConfigDict.ser_json_timedelta]
* [`ser_json_bytes`][pydantic.config.ConfigDict.ser_json_bytes]
* [`ser_json_inf_nan`][pydantic.config.ConfigDict.ser_json_inf_nan]

### Unenforced `Field` constraints

Expand Down
3 changes: 3 additions & 0 deletions pydantic/_internal/_config.py
Expand Up @@ -67,6 +67,7 @@ class ConfigWrapper:
revalidate_instances: Literal['always', 'never', 'subclass-instances']
ser_json_timedelta: Literal['iso8601', 'float']
ser_json_bytes: Literal['utf8', 'base64']
ser_json_inf_nan: Literal['null', 'constants']
# whether to validate default values during validation, default False
validate_default: bool
validate_return: bool
Expand Down Expand Up @@ -167,6 +168,7 @@ def dict_not_none(**kwargs: Any) -> Any:
strict=self.config_dict.get('strict'),
ser_json_timedelta=self.config_dict.get('ser_json_timedelta'),
ser_json_bytes=self.config_dict.get('ser_json_bytes'),
ser_json_inf_nan=self.config_dict.get('ser_json_inf_nan'),
from_attributes=self.config_dict.get('from_attributes'),
loc_by_alias=self.config_dict.get('loc_by_alias'),
revalidate_instances=self.config_dict.get('revalidate_instances'),
Expand Down Expand Up @@ -236,6 +238,7 @@ def push(self, config_wrapper: ConfigWrapper | ConfigDict | None):
revalidate_instances='never',
ser_json_timedelta='iso8601',
ser_json_bytes='utf8',
ser_json_inf_nan='null',
validate_default=False,
validate_return=False,
protected_namespaces=('model_',),
Expand Down
9 changes: 9 additions & 0 deletions pydantic/config.py
Expand Up @@ -540,6 +540,15 @@ class Transaction(BaseModel):
- `'base64'` will serialize bytes to URL safe base64 strings.
"""

ser_json_inf_nan: Literal['null', 'constants']
"""
The encoding of JSON serialized infinity and NaN float values. Accepts the string values of `'null'` and `'constants'`.
Defaults to `'null'`.

- `'null'` will serialize infinity and NaN values as `null`.
- `'constants'` will serialize infinity and NaN values as `Infinity` and `NaN`.
"""

# whether to validate default values during validation, default False
validate_default: bool
"""Whether to validate default values during validation. Defaults to `False`."""
Expand Down
25 changes: 23 additions & 2 deletions tests/test_types.py
Expand Up @@ -38,7 +38,7 @@
import annotated_types
import dirty_equals
import pytest
from dirty_equals import HasRepr, IsOneOf, IsStr
from dirty_equals import HasRepr, IsFloatNan, IsOneOf, IsStr
from pydantic_core import CoreSchema, PydanticCustomError, SchemaError, core_schema
from typing_extensions import Annotated, Literal, TypedDict, get_args

Expand Down Expand Up @@ -2529,7 +2529,7 @@ class Model(BaseModel):
]


def test_finite_float_validation():
def test_infinite_float_validation():
class Model(BaseModel):
a: float = None

Expand All @@ -2538,6 +2538,27 @@ class Model(BaseModel):
assert math.isnan(Model(a=float('nan')).a)


@pytest.mark.parametrize(
('ser_json_inf_nan', 'input', 'output', 'python_roundtrip'),
(
('null', float('inf'), 'null', None),
('null', float('-inf'), 'null', None),
('null', float('nan'), 'null', None),
('constants', float('inf'), 'Infinity', float('inf')),
('constants', float('-inf'), '-Infinity', float('-inf')),
('constants', float('nan'), 'NaN', IsFloatNan),
),
)
def test_infinite_float_json_serialization(ser_json_inf_nan, input, output, python_roundtrip):
class Model(BaseModel):
model_config = ConfigDict(ser_json_inf_nan=ser_json_inf_nan)
a: float

json_string = Model(a=input).model_dump_json()
assert json_string == f'{{"a":{output}}}'
assert json.loads(json_string) == {'a': python_roundtrip}


@pytest.mark.parametrize('value', [float('inf'), float('-inf'), float('nan')])
def test_finite_float_validation_error(value):
class Model(BaseModel):
Expand Down