Numeric Validation and Constraints
Numeric validation in pydantic-core is handled through specialized schema types for integers, floating-point numbers, decimals, and complex numbers. These schemas provide a consistent interface for mathematical constraints while accounting for the unique precision and representation requirements of each type.
Core Numeric Schemas
The codebase defines four primary numeric schemas in pydantic_core.core_schema:
IntSchema: Validates integer values.FloatSchema: Validates floating-point numbers.DecimalSchema: Validatesdecimal.Decimalobjects, offering high precision and fixed-point arithmetic.ComplexSchema: Validates complex numbers.
Shared Mathematical Constraints
IntSchema, FloatSchema, and DecimalSchema support a common set of mathematical constraints:
gt: Greater thange: Greater than or equal tolt: Less thanle: Less than or equal to-
multiple_of: The value must be a multiple of the provided number.
These constraints are applied during validation. For example, an integer schema with multiple constraints:
from pydantic_core import SchemaValidator, core_schema
# Schema requiring an even integer between 2 and 6 (inclusive)
schema = core_schema.int_schema(multiple_of=2, le=6, ge=2)
v = SchemaValidator(schema)
assert v.validate_python(4) == 4
assert v.validate_python('6') == 6
Integer Validation
IntSchema handles the conversion of various inputs into Python int objects.
Lax vs. Strict Mode
In lax mode (the default), pydantic-core attempts to coerce types like strings, booleans, and even "exact" floats into integers.
- Exact Floats: A float like
42.0is accepted because it has no fractional part. - Fractional Floats: A float like
42.5is rejected with anint_from_floaterror.
In strict mode, only actual int instances are accepted.
# Lax mode (default)
v_lax = SchemaValidator(core_schema.int_schema())
assert v_lax.validate_python(42.0) == 42
assert v_lax.validate_python('42') == 42
# Strict mode
v_strict = SchemaValidator(core_schema.int_schema(strict=True))
# v_strict.validate_python(42.0) # Raises ValidationError (int_type)
# v_strict.validate_python('42') # Raises ValidationError (int_type)
Large Integers and Formatting
The validator supports arbitrarily large integers and strings containing underscores for readability (e.g., '1_000_000'). However, it enforces a maximum size for string-to-integer parsing (defaulting to 4300 characters) to prevent potential DoS attacks.
Floating-Point Validation
FloatSchema manages float validation and supports scientific notation and non-finite values.
Non-Finite Values
By default, FloatSchema allows NaN, inf, and -inf. This behavior is controlled by the allow_inf_nan flag, which defaults to True.
v = SchemaValidator(core_schema.float_schema(allow_inf_nan=True))
assert v.validate_python('NaN').is_nan() # Validates to float('nan')
assert v.validate_python('inf') == float('inf')
Precision and Coercion
In lax mode, FloatSchema accepts integers, strings, and booleans (where True becomes 1.0). In strict mode, it only accepts float and int instances (converting the int to a float).
Decimal Validation
DecimalSchema is designed for scenarios requiring exact decimal representation, such as financial calculations.
Precision Constraints
In addition to standard numeric constraints, DecimalSchema supports:
max_digits: Total number of allowed digits.decimal_places: Maximum number of allowed decimal places.
from decimal import Decimal
from pydantic_core import SchemaValidator, core_schema as cs
v = SchemaValidator(cs.decimal_schema(max_digits=5, decimal_places=2))
# Valid: 5 total digits, 2 decimal places
assert v.validate_python('123.45') == Decimal('123.45')
# Invalid: 6 total digits
# v.validate_python('1234.56') # Raises ValidationError
Default Behavior Differences
Unlike FloatSchema, DecimalSchema sets allow_inf_nan to False by default. If you need to support non-finite decimals, you must explicitly enable it.
Strict Mode in JSON vs. Python
DecimalSchema exhibits unique behavior in strict mode:
- Python: Rejects strings and floats; only
Decimalinstances are accepted. - JSON: Accepts numeric strings (e.g.,
"12.34") and numbers, as JSON has no native Decimal type.
Complex Numbers
ComplexSchema validates Python complex objects. Unlike the other numeric types, it does not support mathematical constraints like gt or le.
It supports parsing complex numbers from strings following Python's standard rules (e.g., '1+2j').
v = SchemaValidator(core_schema.complex_schema())
assert v.validate_python('1+2j') == complex(1, 2)
assert v.validate_python(5) == complex(5, 0)
In strict mode, ComplexSchema only accepts actual complex instances in Python, but it will still accept valid complex strings from JSON.
Handling Non-Finite Values in Unions
When using numeric types in a Union, the order and strictness of the branches are critical. For example, if a str schema precedes a Decimal schema, a numeric string like '1.23' will be validated as a string rather than being coerced to a Decimal.
# Prefers string because it matches first
s = SchemaValidator(core_schema.union_schema([
core_schema.str_schema(),
core_schema.decimal_schema()
]))
assert s.validate_python('1.23') == '1.23'
To ensure numeric coercion, place the numeric schema before the string schema or use strict mode where appropriate.