Function and Argument Validation
Function and argument validation in this codebase is implemented as a two-stage process: first, validating the input (positional and keyword arguments) into a structured format, and second, executing a callable with those validated arguments while optionally validating the return value.
This functionality is primarily orchestrated by CallSchema, which relies on ArgumentsSchema or ArgumentsV3Schema to handle the complexity of Python function signatures.
The Call Orchestrator
The CallSchema (defined in pydantic_core/core_schema.py) is the top-level schema that ties a Python function to its argument validation logic. It acts as a wrapper that:
- Validates the input data using an internal
arguments_schema. - Calls the specified
functionwith the resulting arguments. - Validates the return value of the function against an optional
return_schema.
Basic Usage
In pydantic-core/tests/validators/test_call.py, CallSchema is used to wrap a simple addition function:
from pydantic_core import SchemaValidator, core_schema as cs
def my_function(a, b, c):
return a + b + c
v = SchemaValidator({
'type': 'call',
'function': my_function,
'arguments_schema': {
'type': 'arguments',
'arguments_schema': [
{'name': 'a', 'mode': 'positional_or_keyword', 'schema': {'type': 'int'}},
{'name': 'b', 'mode': 'positional_or_keyword', 'schema': {'type': 'int'}},
{'name': 'c', 'mode': 'positional_or_keyword', 'schema': {'type': 'int'}},
],
},
'return_schema': {'type': 'int', 'le': 10},
})
# Validates positional arguments
assert v.validate_python((1, 2, 3)) == 6
# Validates keyword arguments
assert v.validate_python({'a': 1, 'b': 1, 'c': 1}) == 3
If the function returns a value that fails the return_schema (e.g., (3, 3, 5) resulting in 11 when the limit is 10), the validator raises a ValidationError with the location marked as return.
Validating Arguments
The validation engine provides two primary schemas for handling function arguments: ArgumentsSchema and the more modern ArgumentsV3Schema.
ArgumentsSchema
ArgumentsSchema is the standard way to define a function signature. It takes a list of ArgumentsParameter objects, each defining how a specific parameter should be validated.
Each ArgumentsParameter supports several modes:
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.
# Example of a mixed signature in ArgumentsSchema
{
'type': 'arguments',
'arguments_schema': [
{'name': 'pos_only', 'mode': 'positional_only', 'schema': {'type': 'int'}},
{'name': 'either', 'mode': 'positional_or_keyword', 'schema': {'type': 'str'}},
{'name': 'kw_only', 'mode': 'keyword_only', 'schema': {'type': 'bool'}},
],
}
Note: ArgumentsSchema must return a tuple of (args_tuple, kwargs_dict). If you apply a custom validator to an ArgumentsSchema, it must preserve this structure, or CallSchema will fail when attempting to call the function.
ArgumentsV3Schema
ArgumentsV3Schema is an improved version (found in pydantic_core/core_schema.py) designed to handle more complex Python 3.11+ features like Unpack and variadic arguments more naturally. It uses ArgumentsV3Parameter, which introduces specialized modes:
var_args: Handles*args.var_kwargs_uniform: Handles**kwargswhere all values share the same schema.var_kwargs_unpacked_typed_dict: Handles**kwargsthat are defined by aTypedDict(usingUnpack).
In pydantic/experimental/arguments_schema.py, the generate_arguments_schema function defaults to using arguments-v3 because of its superior handling of these edge cases.
Automated Schema Generation
While schemas can be constructed manually, the codebase provides utilities to generate them directly from Python functions using type hints.
The pydantic.experimental.arguments_schema.generate_arguments_schema function inspects a function's signature and produces the appropriate CoreSchema.
from pydantic.experimental.arguments_schema import generate_arguments_schema
from pydantic_core import SchemaValidator
def complex_func(a: int, *args: int, b: str, **kwargs: float):
return None
# Generates an ArgumentsV3Schema
schema = generate_arguments_schema(complex_func)
v = SchemaValidator(schema)
# Input is validated into the ((args), {kwargs}) structure
result = v.validate_python({'a': 1, 'args': [2, 3], 'b': 'hello', 'kwargs': {'c': 1.5}})
assert result == ((1, 2, 3), {'b': 'hello', 'c': 1.5})
Callable Validation
For cases where you only need to verify that a value is a callable (without validating its signature or calling it), the engine provides CallableSchema.
# pydantic_core/core_schema.py
class CallableSchema(TypedDict, total=False):
type: Required[Literal['callable']]
ref: str
metadata: dict[str, Any]
serialization: SerSchema
This schema performs a simple callable(value) check during validation. It is often used as a building block for higher-level types that expect function-like objects.