]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
🐛 Fix `TypeError` when encoding a decimal with a `NaN` or `Infinity` value (#12935)
authorKent Huang <kentwelcome@gmail.com>
Tue, 2 Dec 2025 03:31:59 +0000 (11:31 +0800)
committerGitHub <noreply@github.com>
Tue, 2 Dec 2025 03:31:59 +0000 (04:31 +0100)
Signed-off-by: Kent Huang <kent@infuseai.io>
fastapi/encoders.py
tests/test_jsonable_encoder.py

index 6fc6228e157ac83ee003dd2f9ee6ef9e7c074244..7939510895503e05d436d90506aa06fe13a95df0 100644 (file)
@@ -34,14 +34,14 @@ def isoformat(o: Union[datetime.date, datetime.time]) -> str:
     return o.isoformat()
 
 
-# Taken from Pydantic v1 as is
+# Adapted from Pydantic v1
 # TODO: pv2 should this return strings instead?
 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)
-    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.
     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"))
     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)
     else:
         return float(dec_value)
index 447c5b4d6a63ce0b5638bac4a74f99ff95eae767..3b6513e27bf40eb3f85067180e7e4facb8041b3a 100644 (file)
@@ -3,6 +3,7 @@ from dataclasses import dataclass
 from datetime import datetime, timezone
 from decimal import Decimal
 from enum import Enum
+from math import isinf, isnan
 from pathlib import PurePath, PurePosixPath, PureWindowsPath
 from typing import Optional
 
@@ -306,6 +307,20 @@ def test_decimal_encoder_int():
     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():
     class Model(BaseModel):
         test: str