The validate_call decorator enables validation of function arguments and optionally return values, turning regular Python functions into validated functions.
Decorator
validate_call
def validate_call(
func: AnyCallableT | None = None,
/,
*,
config: ConfigDict | None = None,
validate_return: bool = False,
) -> AnyCallableT | Callable[[AnyCallableT], AnyCallableT]
Returns a decorated wrapper around a function that validates the arguments and, optionally, the return value.
func
Callable | None
default:"None"
The function to be decorated. When None, returns a decorator function. This allows the decorator to be used with or without parentheses.
config
ConfigDict | None
default:"None"
Optional configuration dictionary to customize validation behavior. Accepts the same options as BaseModel.model_config.
Whether to validate the return value of the function. When True, the function’s return type annotation is validated.
Usage
The decorator can be used in two ways:
Without Arguments
from pydantic import validate_call
@validate_call
def repeat(s: str, count: int) -> str:
return s * count
repeat('hello', 2)
#> 'hellohello'
repeat('hello', '2') # String gets coerced to int
#> 'hellohello'
try:
repeat('hello', 'invalid')
except ValidationError as e:
print(e)
# 1 validation error for repeat
# 1
# Input should be a valid integer
With Arguments
from pydantic import validate_call, ConfigDict
@validate_call(config=ConfigDict(strict=True), validate_return=True)
def multiply(a: int, b: int) -> int:
return a * b
multiply(2, 3)
#> 6
try:
multiply('2', 3) # Strict mode: no coercion
except ValidationError as e:
print(e)
# 1 validation error for multiply
# 0
# Input should be a valid integer
Validating Return Values
When validate_return=True, the function’s return value is validated against its return type annotation:
from pydantic import validate_call, ValidationError
@validate_call(validate_return=True)
def get_number() -> int:
return "not a number" # This will raise ValidationError
try:
get_number()
except ValidationError as e:
print(e)
# 1 validation error for get_number
# return
# Input should be a valid integer
Configuration
You can pass any ConfigDict options to customize validation behavior:
from pydantic import validate_call, ConfigDict
@validate_call(
config=ConfigDict(
strict=True, # Enable strict mode (no coercion)
validate_default=True, # Validate default values
arbitrary_types_allowed=True, # Allow arbitrary types
)
)
def process_data(data: CustomType, flag: bool = True) -> dict:
return {'data': data, 'flag': flag}
Supported Function Types
The validate_call decorator supports:
- Regular functions
- Methods (instance, class, and static methods)
- Lambda functions
functools.partial wrapped functions
Apply decorators in the correct order. The @validate_call decorator should be applied before (below) @classmethod, @staticmethod, or @property decorators:class MyClass:
@classmethod
@validate_call # Correct: validate_call below classmethod
def my_method(cls, value: int) -> int:
return value
Limitations
The following are not supported by validate_call:
- Built-in functions (e.g.,
len, print)
- Classes (use Pydantic models instead)
- Callable instances (apply to
__call__ method directly)
Type Annotations
The decorator uses the function’s type annotations to determine how to validate arguments:
from typing import List, Optional
from pydantic import validate_call
@validate_call
def process_items(
items: List[int],
multiplier: float = 1.0,
prefix: Optional[str] = None,
) -> List[float]:
result = [item * multiplier for item in items]
return result
process_items([1, 2, 3], 2.5)
#> [2.5, 5.0, 7.5]
process_items(['1', '2', '3'], '2') # Coercion works
#> [2.0, 4.0, 6.0]
Error Handling
Validation errors are raised as ValidationError exceptions:
from pydantic import validate_call, ValidationError
@validate_call
def divide(a: int, b: int) -> float:
return a / b
try:
divide(10, 'zero')
except ValidationError as e:
print(e.errors())
# [
# {
# 'type': 'int_parsing',
# 'loc': (1,),
# 'msg': 'Input should be a valid integer',
# 'input': 'zero',
# }
# ]
The validate_call decorator adds validation overhead to function calls. For performance-critical code:
- Consider validating only at API boundaries
- Use
ConfigDict(arbitrary_types_allowed=True) to skip validation for complex types
- Profile your code to measure the impact
Examples
Validating a Method
from pydantic import validate_call
class Calculator:
@validate_call
def add(self, a: int, b: int) -> int:
return a + b
@classmethod
@validate_call
def multiply(cls, a: int, b: int) -> int:
return a * b
calc = Calculator()
calc.add(5, 3)
#> 8
Calculator.multiply(4, 2)
#> 8
Using with Partial Functions
from functools import partial
from pydantic import validate_call
@validate_call
def power(base: int, exponent: int) -> int:
return base ** exponent
square = partial(power, exponent=2)
square(5)
#> 25
square('3') # Coercion works through partial
#> 9
Custom Validation with Config
from pydantic import validate_call, ConfigDict, field_validator
from pydantic.dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
def distance(p1: Point, p2: Point) -> float:
return ((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2) ** 0.5
point1 = Point(x=0, y=0)
point2 = Point(x=3, y=4)
distance(point1, point2)
#> 5.0