Function and Argument Validation
Function and argument validation in this codebase is primarily handled by a set of core schemas that bridge the gap between raw data validation and Python function execution. The system is designed to take various input formats (like tuples, dictionaries, or specialized ArgsKwargs objects), validate them against a function's signature, execute the function, and optionally validate the return value.
The Call Lifecycle
The central component for function validation is the CallSchema. It orchestrates the entire process of validating inputs, calling a target function, and validating the output.
CallSchema
Defined in pydantic_core/core_schema.py, the CallSchema acts as a wrapper around a Python callable. It requires an arguments_schema to handle the input mapping and a function to execute.
from pydantic_core import SchemaValidator, core_schema as cs
def my_function(a: int, b: int) -> int:
return a + b
# A CallSchema that validates two integers and calls my_function
schema = cs.call_schema(
arguments=cs.arguments_schema([
cs.arguments_parameter(name='a', schema=cs.int_schema()),
cs.arguments_parameter(name='b', schema=cs.int_schema()),
]),
function=my_function,
return_schema=cs.int_schema()
)
v = SchemaValidator(schema)
# Validating and calling with positional arguments
assert v.validate_python((1, 2)) == 3
The CallSchema follows this internal flow:
- Argument Validation: The input is passed to the
arguments_schema. This schema must return a tuple in the format(args_tuple, kwargs_dict). - Function Execution: The
functionis called using the validatedargs_tupleandkwargs_dict. - Return Validation: If a
return_schemais provided, the result of the function call is validated against it before being returned to the caller.
Defining Function Arguments
The mapping from input data to function parameters is handled by ArgumentsSchema or the newer ArgumentsV3Schema. These schemas define how positional and keyword arguments are extracted and validated.
ArgumentsSchema and ArgumentsParameter
ArgumentsSchema is the standard way to define a function's signature. It uses a list of ArgumentsParameter objects to describe individual parameters.
Each ArgumentsParameter has a mode that determines how it can be supplied:
positional_only: Must be provided as a positional argument.positional_or_keyword: Can be provided either by position or by name (default).keyword_only: Must be provided as a keyword argument.
# Example of different parameter modes in ArgumentsSchema
args_schema = cs.arguments_schema([
cs.arguments_parameter(name='a', schema=cs.int_schema(), mode='positional_only'),
cs.arguments_parameter(name='b', schema=cs.int_schema(), mode='positional_or_keyword'),
cs.arguments_parameter(name='c', schema=cs.int_schema(), mode='keyword_only'),
])
For variadic arguments (*args and **kwargs), ArgumentsSchema provides top-level fields:
var_args_schema: A schema to validate all extra positional arguments.var_kwargs_schema: A schema to validate extra keyword arguments.var_kwargs_mode: Determines if keyword arguments are'uniform'(all same type) or'unpacked-typed-dict'.
ArgumentsV3Schema (Experimental)
ArgumentsV3Schema is a modern alternative found in pydantic_core/core_schema.py. It simplifies the structure by moving variadic logic directly into the ArgumentsV3Parameter modes.
Supported modes in ArgumentsV3Parameter include:
var_args: Represents*args.var_kwargs_uniform: Represents**kwargswhere all values share a schema.var_kwargs_unpacked_typed_dict: Represents**kwargsmapped to aTypedDict.
# Using ArgumentsV3Schema for complex signatures
v3_schema = cs.arguments_v3_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'),
])
v = SchemaValidator(v3_schema)
# Returns ((a, *args), {**kwargs})
assert v.validate_python({'a': 1, 'args': [2, 3], 'kwargs': {'key': 'val'}}) == ((1, 2, 3), {'key': 'val'})
Basic Callable Validation
For scenarios where you only need to verify if a value is a callable (without necessarily calling it or validating its internal signature), the codebase provides CallableSchema.
# Simple check if the input is a Python callable
schema = cs.callable_schema()
v = SchemaValidator(schema)
assert v.validate_python(lambda x: x) == (lambda x: x)
# v.validate_python(123) # Raises ValidationError
Integration with Pydantic
While these schemas are defined in pydantic-core, they are heavily utilized by the main Pydantic library, specifically for the @validate_call decorator.
- Schema Generation:
pydantic/_internal/_generate_schema.pycontains logic to inspect Python function signatures and automatically generate the correspondingArgumentsSchema. - Validation Logic:
pydantic/_internal/_validate_call.pyuses these generated schemas to wrap functions, ensuring that every call to a decorated function is validated against its definedCoreSchema.
When using CallSchema manually, the input to validate_python can be a tuple (for positional-only functions), a dict (for keyword-heavy functions), or an ArgsKwargs object from pydantic_core which explicitly separates the two. Regardless of the input format, the ArgumentsSchema ensures the function receives the correct Python calling convention.