🐛 Fix `TypeError` when encoding a decimal with a `NaN` or `Infinity` value (#12935)

Signed-off-by: Kent Huang <kent@infuseai.io>
This commit is contained in:
Kent Huang 2025-12-02 11:31:59 +08:00 committed by GitHub
parent ee490906d8
commit 20f40b29c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 23 additions and 4 deletions

View File

@ -34,14 +34,14 @@ def isoformat(o: Union[datetime.date, datetime.time]) -> str:
return o.isoformat() return o.isoformat()
# Taken from Pydantic v1 as is # Adapted from Pydantic v1
# TODO: pv2 should this return strings instead? # TODO: pv2 should this return strings instead?
def decimal_encoder(dec_value: Decimal) -> Union[int, float]: def decimal_encoder(dec_value: Decimal) -> Union[int, float]:
""" """
Encodes a Decimal as int of there's no exponent, otherwise float Encodes a Decimal as int if there's no exponent, otherwise float
This is useful when we use ConstrainedDecimal to represent Numeric(x,0) This is useful when we use ConstrainedDecimal to represent Numeric(x,0)
where a integer (but not int typed) is used. Encoding this as a float where an integer (but not int typed) is used. Encoding this as a float
results in failed round-tripping between encode and parse. results in failed round-tripping between encode and parse.
Our Id type is a prime example of this. Our Id type is a prime example of this.
@ -50,8 +50,12 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]:
>>> decimal_encoder(Decimal("1")) >>> decimal_encoder(Decimal("1"))
1 1
>>> decimal_encoder(Decimal("NaN"))
nan
""" """
if dec_value.as_tuple().exponent >= 0: # type: ignore[operator] exponent = dec_value.as_tuple().exponent
if isinstance(exponent, int) and exponent >= 0:
return int(dec_value) return int(dec_value)
else: else:
return float(dec_value) return float(dec_value)

View File

@ -3,6 +3,7 @@ from dataclasses import dataclass
from datetime import datetime, timezone from datetime import datetime, timezone
from decimal import Decimal from decimal import Decimal
from enum import Enum from enum import Enum
from math import isinf, isnan
from pathlib import PurePath, PurePosixPath, PureWindowsPath from pathlib import PurePath, PurePosixPath, PureWindowsPath
from typing import Optional from typing import Optional
@ -306,6 +307,20 @@ def test_decimal_encoder_int():
assert jsonable_encoder(data) == {"value": 2} assert jsonable_encoder(data) == {"value": 2}
@needs_pydanticv2
def test_decimal_encoder_nan():
data = {"value": Decimal("NaN")}
assert isnan(jsonable_encoder(data)["value"])
@needs_pydanticv2
def test_decimal_encoder_infinity():
data = {"value": Decimal("Infinity")}
assert isinf(jsonable_encoder(data)["value"])
data = {"value": Decimal("-Infinity")}
assert isinf(jsonable_encoder(data)["value"])
def test_encode_deque_encodes_child_models(): def test_encode_deque_encodes_child_models():
class Model(BaseModel): class Model(BaseModel):
test: str test: str