Skip to main content

Validating Function Arguments and Calls

Function validation in this codebase is split into two primary concerns: verifying that a value is a callable entity and validating the arguments passed to a function during an actual call.

The core logic resides in pydantic_core.core_schema, which provides schemas for defining function signatures, handling variadic arguments, and executing functions with validated inputs.

Validating Callables

The simplest form of validation is checking if a value is callable. The CallableSchema (defined in pydantic_core/core_schema.py) performs a check equivalent to Python's callable() function.

from pydantic_core import SchemaValidator, core_schema as cs

v = SchemaValidator(cs.callable_schema())

def my_func():
pass

assert v.validate_python(my_func) == my_func
# v.validate_python(123) # Raises ValidationError

Defining Function Signatures

To validate the arguments themselves, the codebase uses ArgumentsSchema. This schema maps input data—which can be a tuple of positional arguments, a dict of keyword arguments, or a specialized ArgsKwargs object—into a validated (args, kwargs) tuple.

ArgumentsParameter and Modes

The ArgumentsSchema relies on a list of ArgumentsParameter objects. Each parameter defines how a specific argument should be validated and which "mode" it uses:

  • positional_only: The argument must be provided positionally.
  • positional_or_keyword: The argument can be provided either way (default).
  • keyword_only: The argument must be provided as a keyword.
from pydantic_core.core_schema import ArgumentsParameter

# Example of a parameter definition
param = ArgumentsParameter(
name='a',
mode='positional_or_keyword',
schema={'type': 'int'},
alias='alpha'
)

ArgumentsSchema Structure

The ArgumentsSchema handles the mapping logic. It can be configured to validate by name or alias and can handle variadic arguments (*args) and keyword arguments (**kwargs) via var_args_schema and var_kwargs_schema.

from pydantic_core import SchemaValidator, core_schema as cs

v = SchemaValidator({
'type': 'arguments',
'arguments_schema': [
{'name': 'a', 'mode': 'positional_only', 'schema': {'type': 'int'}},
{'name': 'b', 'mode': 'keyword_only', 'schema': {'type': 'str'}},
],
})

# Validating a mix of positional and keyword inputs
# Returns (args_tuple, kwargs_dict)
args, kwargs = v.validate_python(((1,), {'b': 'hello'}))
assert args == (1,)
assert kwargs == {'b': 'hello'}

Executing Validated Calls

The CallSchema integrates an ArgumentsSchema with a specific Python function. When validate_python is called on a CallSchema, the validator:

  1. Validates the input against the arguments_schema.
  2. Calls the provided function using the validated (args, kwargs).
  3. Optionally validates the return value of the function against a return_schema.

This pattern is used extensively in tests like pydantic-core/tests/validators/test_call.py.

from pydantic_core import SchemaValidator, core_schema as cs

def add(a: int, b: int) -> int:
return a + b

v = SchemaValidator({
'type': 'call',
'function': add,
'arguments_schema': {
'type': 'arguments',
'arguments_schema': [
{'name': 'a', 'mode': 'positional_or_keyword', 'schema': {'type': 'int'}},
{'name': 'b', 'mode': 'positional_or_keyword', 'schema': {'type': 'int'}},
],
},
'return_schema': {'type': 'int', 'lt': 100},
})

# The result of validation is the result of the function call
assert v.validate_python({'a': 10, 'b': 20}) == 30

If the function call fails or the return value does not match the return_schema, a ValidationError is raised. The function_name field in CallSchema can be used to customize the error location; otherwise, it defaults to function.__name__.

Advanced Signatures with ArgumentsV3

The codebase includes an experimental ArgumentsV3Schema and ArgumentsV3Parameter. This version simplifies the definition of complex signatures by including variadic arguments directly in the main arguments_schema list using specialized modes.

ArgumentsV3Parameter supports additional modes:

  • var_args: Equivalent to *args.
  • var_kwargs_uniform: Equivalent to **kwargs where all values share a schema.
  • var_kwargs_unpacked_typed_dict: Used for PEP 646 style unpacked TypedDicts.

Example from pydantic-core/tests/validators/arguments_v3/test_general.py:

from pydantic_core import core_schema as cs, ArgsKwargs

v = SchemaValidator(
cs.arguments_v3_schema(
arguments_schema=[
cs.arguments_v3_parameter(name='a', schema=cs.int_schema(), mode='positional_only'),
cs.arguments_v3_parameter(name='args', schema=cs.int_schema(), mode='var_args'),
cs.arguments_v3_parameter(name='kwargs', schema=cs.str_schema(), mode='var_kwargs_uniform'),
]
)
)

# Validating against a mix of positional and variadic inputs
input_data = ArgsKwargs((1, 2, 3), {'extra': 'value'})
validated = v.validate_python(input_data)
# Result: ((1, 2, 3), {'extra': 'value'})

Unlike the standard ArgumentsSchema, ArgumentsV3Schema uses extra_behavior (set to 'forbid' or 'ignore') to control how unexpected keyword arguments are handled, providing stricter control over function invocation.