Repository: pydantic/pydantic

Issue:
Allow Infinity, -Infinity and NaN values on Decimal field
# Feature Request

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
 pydantic version: 1.4
            pydantic compiled: False
                 install path: <virtual env path>
               python version: 3.8.2 (v3.8.2:7b3ab5921f, Feb 24 2020, 17:52:18)  [Clang 6.0 (clang-600.0.57)]
                     platform: macOS-10.15.6-x86_64-i386-64bit
     optional deps. installed: []
```
<!-- or if you're using pydantic prior to v1.3, manually include: OS, python version and pydantic version -->

<!-- Please read the [docs](https://pydantic-docs.helpmanual.io/) and search through issues to
confirm your feature hasn't been asked for before, or already implemented. -->

<!-- Where possible please include a self-contained code snippet describing your feature request: -->
`Infinity`, `-Infinity` and `NaN` decimal values should be allowed.
```py
import pydantic
from decimal import Decimal

class MyModel(pydantic.BaseModel):
    value: Decimal

m = MyModel(value="infinity")
assert m.value == Decimal("inf")
```

Currently this code would raise an error:
```
pydantic.error_wrappers.ValidationError: 1 validation error for MyModel
value
  value is not a valid decimal (type=value_error.decimal.not_finite)
```

This is caused by a check in the [`decimal_validator`](https://github.com/samuelcolvin/pydantic/blob/master/pydantic/validators.py#L294).
This is inconsistent, because:
* `Infinity`, `-Infinity` and `NaN` are valid Decimal values.
* if we pass `Decimal` instance as a value (not a string) - there is no error
```
In[1]: MyModel(value=Decimal("inf"))
Out[1]: MyModel(value=Decimal('Infinity'))
```

Also, docs do not state that those values are excluded - https://pydantic-docs.helpmanual.io/usage/types/#constrained-types
> decimal.Decimal
    pydantic attempts to convert the value to a string, then passes the string to Decimal(v)



Discussion:
The reason for this might be because, while those values are valid IEEE 754 floating point values (so they would be accepted both by Python's `float` and `Decimal` types), they are illegal in well-formed JSON according to [the JSON spec (RFC 7159), Section 6](https://tools.ietf.org/html/rfc7159#section-6):

> Numeric values that cannot be represented in the grammar below (such as Infinity and NaN) are not permitted.

With that being said, even though Pydantic usually follows very JSON-like parsing rules, it performs no such check when parsing `float` (which are supposed to follow the same restrictions) or `int` (which are not guaranteed to be correctly readable by all JSON parsers if they take up more than 53 bits) values, so removing that check would at least be consistent with other types.

NaN and Infinity do also this null-like quality to them in that they work unlike every other possible value (neither of them are considered to be equal to themselves and `NaN < 0 == False` but also `NaN >= 0 == False`, so adding those values makes lists of floats potentially unorderable), so there would be good reasons to leave them out, or at least make them opt-in.
I would also warn that, as unlikely as it may seem, it's hard to be certain that code might not be written by a pydantic consumer that depends upon these errors for functionality.  This would therefore represent a theoretically breaking change to the interface.

I echo @sm-Fifteen's concerns that there are a variety of reasons why Infinity and NaN might be excluded from the basic definition.  I would recommend that instead of changing the behavior of the decimal handler, we consider adding an alternate type for people who would like this functionality.  Something like `UnboundedDecimal` (name TBD) which would allow for these values, and also be explicit in documentation about how the value might be represented when calling `.json`.

Alternatively, it's certainly confusing that not all valid `Decimal` values are valid for pydantic, so maybe we could add `BoundedDecimal` while changing the basic `Decimal` behavior, but we'd need to still be very careful about the dangers of a breaking change.
The issue I see with the current state of  the `decimal_validator` is that passing `Decimal("Infinity")` is allowed, but when passing the string `"Infinity"` the validation fails (as mentioned in the second bullet point by @umatbro).

```python
from decimal import Decimal

from pydantic import BaseModel, ValidationError


class Model(BaseModel):
    n: Decimal


try:
    Model(n="Infinity")
except ValidationError as e:
    print(str(e))
    """
    1 validation error for Model
    n
      value is not a valid decimal (type=value_error.decimal.not_finite)
    """

validated_model = Model(n=Decimal("Infinity"))
print(validated_model.n)
"Infinity"
```

This is because the first thing `decimal_validator` checks is if the provided value is a `Decimal` and if so, returns the object.

IMO before returning we should also check that the Decimal is not finite and raise accordingly. (Then it would be consistent with passing a string.)
Another way would be to remove the `isinstance` check and let the decimal be converted to a string and carry on with the rest of the function. So the way I see it, either code is duplicated OR unnecessary conversions are done in case an actual `Decimal` is passed. 

Resolving patch:
diff --git a/pydantic/__init__.py b/pydantic/__init__.py
--- a/pydantic/__init__.py
+++ b/pydantic/__init__.py
@@ -131,6 +131,7 @@
     'FutureDate',
     'AwareDatetime',
     'NaiveDatetime',
+    'AllowInfNan',
     # version
     'VERSION',
 ]
diff --git a/pydantic/types.py b/pydantic/types.py
--- a/pydantic/types.py
+++ b/pydantic/types.py
@@ -72,6 +72,7 @@
     'condate',
     'AwareDatetime',
     'NaiveDatetime',
+    'AllowInfNan',
 ]
 
 from ._internal._core_metadata import build_metadata_dict


Test patch:
diff --git a/tests/test_types.py b/tests/test_types.py
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -84,7 +84,7 @@
     constr,
 )
 from pydantic.decorators import field_validator
-from pydantic.types import ImportString, SecretField, Strict
+from pydantic.types import AllowInfNan, ImportString, SecretField, Strict
 
 try:
     import email_validator
@@ -863,6 +863,27 @@ class Model(BaseModel):
     assert m.model_dump() == {'v': Decimal('1.234')}
 
 
+def test_decimal_allow_inf():
+    class MyModel(BaseModel):
+        value: Annotated[Decimal, AllowInfNan(True)]
+
+    m = MyModel(value='inf')
+    assert m.value == Decimal('inf')
+
+    m = MyModel(value=Decimal('inf'))
+    assert m.value == Decimal('inf')
+
+
+def test_decimal_dont_allow_inf():
+    class MyModel(BaseModel):
+        value: Decimal
+
+    with pytest.raises(ValidationError, match=r'Input should be a finite number \[type=finite_number'):
+        MyModel(value='inf')
+    with pytest.raises(ValidationError, match=r'Input should be a finite number \[type=finite_number'):
+        MyModel(value=Decimal('inf'))
+
+
 def test_decimal_strict():
     class Model(BaseModel):
         v: Decimal