Skip to main content

Validating Function Arguments and Calls

In this tutorial, you will learn how to use specialized schemas in pydantic-core to validate function arguments and execute calls. This is particularly useful when you need to enforce strict types on function inputs or wrap class constructors (like Dataclasses or NamedTuples) with validation logic.

Prerequisites

To follow this tutorial, you need pydantic-core installed. You should be familiar with the basic SchemaValidator and how CoreSchema dictionaries are structured.

from pydantic_core import SchemaValidator, core_schema, ArgsKwargs

Step 1: Defining Function Parameters

The foundation of argument validation is the ArgumentsParameter. It describes a single parameter's name, its validation schema, and its "mode" (how it can be passed).

Let's define parameters for a simple addition function:

# Define parameters for a function: add(a: int, b: int, c: int = 0)
params = [
{'name': 'a', 'mode': 'positional_only', 'schema': core_schema.int_schema()},
{'name': 'b', 'mode': 'positional_or_keyword', 'schema': core_schema.int_schema()},
{
'name': 'c',
'mode': 'keyword_only',
'schema': core_schema.default_schema(schema=core_schema.int_schema(), default=0)
},
]

In this example:

  • a is positional-only.
  • b can be passed positionally or by name.
  • c is keyword-only and has a default value of 0.

Step 2: Validating Raw Arguments

The ArgumentsSchema uses these parameters to validate a set of inputs. Unlike standard models, validating an ArgumentsSchema returns a tuple containing a tuple of positional arguments and a dictionary of keyword arguments: ((args), {kwargs}).

schema = core_schema.arguments_schema(arguments=params)
v = SchemaValidator(schema)

# Validate a mix of positional and keyword arguments
# We use ArgsKwargs to pass both to the validator
result = v.validate_python(ArgsKwargs((1, 2), {'c': 3}))
print(result)
# Output: ((1, 2), {'c': 3})

# Validate using only positional arguments (where allowed)
result = v.validate_python((1, 5))
print(result)
# Output: ((1, 5), {'c': 0})

If you provide an invalid input, such as passing a keyword argument for a positional-only parameter, pydantic-core will raise a ValidationError.

Step 3: Executing Validated Calls

While ArgumentsSchema parses the inputs, CallSchema goes a step further by actually executing a function with those validated inputs.

def my_function(a, b, c):
return a + b + c

call_schema = core_schema.call_schema(
arguments=core_schema.arguments_schema(arguments=params),
function=my_function
)
v = SchemaValidator(call_schema)

# This validates the input AND calls the function
final_result = v.validate_python(ArgsKwargs((10, 20), {'c': 5}))
print(final_result)
# Output: 35

CallSchema is highly versatile and can wrap any callable, including class constructors:

from dataclasses import dataclass

@dataclass
class User:
id: int
name: str

user_schema = core_schema.call_schema(
arguments=core_schema.arguments_schema(arguments=[
{'name': 'id', 'schema': core_schema.int_schema()},
{'name': 'name', 'schema': core_schema.str_schema()},
]),
function=User
)
v_user = SchemaValidator(user_schema)

user = v_user.validate_python({'id': '123', 'name': 'Alice'})
print(user)
# Output: User(id=123, name='Alice')

Step 4: Validating Return Values

You can also enforce a schema on the value returned by the function using the return_schema field in CallSchema.

call_schema_with_return = core_schema.call_schema(
arguments=core_schema.arguments_schema(arguments=params),
function=my_function,
return_schema=core_schema.int_schema(le=100) # Result must be <= 100
)
v_limit = SchemaValidator(call_schema_with_return)

# This works
print(v_limit.validate_python((10, 20))) # 30

# This raises a ValidationError because the return value (150) > 100
# v_limit.validate_python((50, 100))

Step 5: Handling Variadic Arguments (*args and **kwargs)

To handle functions with *args or **kwargs, use var_args_schema and var_kwargs_schema within the ArgumentsSchema.

variadic_schema = core_schema.arguments_schema(
arguments=[
{'name': 'fixed', 'schema': core_schema.str_schema()}
],
var_args_schema=core_schema.int_schema(), # All *args must be ints
var_kwargs_schema=core_schema.float_schema() # All **kwargs must be floats
)
v_var = SchemaValidator(variadic_schema)

# Validating: fixed='hello', args=(1, 2, 3), kwargs={'extra': 1.5}
res = v_var.validate_python(ArgsKwargs(('hello', 1, 2, 3), {'extra': 1.5}))
print(res)
# Output: (('hello', 1, 2, 3), {'extra': 1.5})

Next Steps

Manually defining ArgumentsParameter lists can be tedious for complex functions. In the pydantic library, this process is often automated. You can explore the experimental generate_arguments_schema helper to automatically create these schemas from Python function signatures:

from pydantic.experimental.arguments_schema import generate_arguments_schema

def complex_func(a: int, b: str = 'default'):
return f"{a} {b}"

# Automatically generates an ArgumentsV3Schema
schema = generate_arguments_schema(complex_func)

The ArgumentsV3Schema is an updated version of the arguments schema designed for Pydantic V3, offering better support for extra_behavior (like forbidding unexpected arguments) and unified variadic parameter handling via ArgumentsV3Parameter.