Skip to content

Commit e61d006

Browse files
committed
Add new arguments schema
1 parent aa142f9 commit e61d006

File tree

7 files changed

+732
-0
lines changed

7 files changed

+732
-0
lines changed

python/pydantic_core/core_schema.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3494,6 +3494,112 @@ def arguments_schema(
34943494
)
34953495

34963496

3497+
class ArgumentsV3Parameter(TypedDict, total=False):
3498+
name: Required[str]
3499+
schema: Required[CoreSchema]
3500+
mode: Literal[
3501+
'positional_only',
3502+
'positional_or_keyword',
3503+
'keyword_only',
3504+
'var_args',
3505+
'var_kwargs_uniform',
3506+
'var_kwargs_unpacked_typed_dict',
3507+
] # default positional_or_keyword
3508+
alias: Union[str, list[Union[str, int]], list[list[Union[str, int]]]]
3509+
3510+
3511+
def arguments_v3_parameter(
3512+
name: str,
3513+
schema: CoreSchema,
3514+
*,
3515+
mode: Literal[
3516+
'positional_only',
3517+
'positional_or_keyword',
3518+
'keyword_only',
3519+
'var_args',
3520+
'var_kwargs_uniform',
3521+
'var_kwargs_unpacked_typed_dict',
3522+
]
3523+
| None = None,
3524+
alias: str | list[str | int] | list[list[str | int]] | None = None,
3525+
) -> ArgumentsV3Parameter:
3526+
"""
3527+
Returns a schema that matches an argument parameter, e.g.:
3528+
3529+
```py
3530+
from pydantic_core import SchemaValidator, core_schema
3531+
3532+
param = core_schema.arguments_v3_parameter(
3533+
name='a', schema=core_schema.str_schema(), mode='positional_only'
3534+
)
3535+
schema = core_schema.arguments_v3_schema([param])
3536+
v = SchemaValidator(schema)
3537+
assert v.validate_python({'a': 'hello'}) == (('hello',), {})
3538+
```
3539+
3540+
Args:
3541+
name: The name to use for the argument parameter
3542+
schema: The schema to use for the argument parameter
3543+
mode: The mode to use for the argument parameter
3544+
alias: The alias to use for the argument parameter
3545+
"""
3546+
return _dict_not_none(name=name, schema=schema, mode=mode, alias=alias)
3547+
3548+
3549+
class ArgumentsV3Schema(TypedDict, total=False):
3550+
type: Required[Literal['arguments-v3']]
3551+
arguments_schema: Required[list[ArgumentsV3Parameter]]
3552+
populate_by_name: bool
3553+
var_args_schema: CoreSchema
3554+
var_kwargs_mode: VarKwargsMode
3555+
var_kwargs_schema: CoreSchema
3556+
ref: str
3557+
metadata: dict[str, Any]
3558+
serialization: SerSchema
3559+
3560+
3561+
def arguments_v3_schema(
3562+
arguments: list[ArgumentsV3Parameter],
3563+
*,
3564+
populate_by_name: bool | None = None,
3565+
ref: str | None = None,
3566+
metadata: dict[str, Any] | None = None,
3567+
serialization: SerSchema | None = None,
3568+
) -> ArgumentsV3Schema:
3569+
"""
3570+
Returns a schema that matches an arguments schema, e.g.:
3571+
3572+
```py
3573+
from pydantic_core import SchemaValidator, core_schema
3574+
3575+
param_a = core_schema.arguments_v3_parameter(
3576+
name='a', schema=core_schema.str_schema(), mode='positional_only'
3577+
)
3578+
param_b = core_schema.arguments_v3_parameter(
3579+
name='kwargs', schema=core_schema.bool_schema(), mode='var_kwargs_uniform'
3580+
)
3581+
schema = core_schema.arguments_v3_schema([param_a, param_b])
3582+
v = SchemaValidator(schema)
3583+
assert v.validate_python({'a': 'hello', 'kwargs': {'extra': True}}) == (('hello',), {'extra': True})
3584+
```
3585+
3586+
Args:
3587+
arguments: The arguments to use for the arguments schema
3588+
populate_by_name: Whether to populate by name
3589+
ref: optional unique identifier of the schema, used to reference the schema in other places
3590+
metadata: Any other information you want to include with the schema, not used by pydantic-core
3591+
serialization: Custom serialization schema
3592+
"""
3593+
return _dict_not_none(
3594+
type='arguments-v2',
3595+
arguments_schema=arguments,
3596+
populate_by_name=populate_by_name,
3597+
ref=ref,
3598+
metadata=metadata,
3599+
serialization=serialization,
3600+
)
3601+
3602+
34973603
class CallSchema(TypedDict, total=False):
34983604
type: Required[Literal['call']]
34993605
arguments_schema: Required[CoreSchema]

src/input/input_abstract.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ pub trait Input<'py>: fmt::Debug {
8181

8282
fn validate_args(&self) -> ValResult<Self::Arguments<'_>>;
8383

84+
fn validate_args_v3(&self) -> ValResult<Self::Arguments<'_>>;
85+
8486
fn validate_dataclass_args<'a>(&'a self, dataclass_name: &str) -> ValResult<Self::Arguments<'a>>;
8587

8688
fn validate_str(&self, strict: bool, coerce_numbers_to_str: bool) -> ValMatch<EitherString<'_>>;

src/input/input_json.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ impl<'py, 'data> Input<'py> for JsonValue<'data> {
8585
}
8686
}
8787

88+
#[cfg_attr(has_coverage_attribute, coverage(off))]
89+
fn validate_args_v3(&self) -> ValResult<Self::Arguments<'_>> {
90+
Err(ValError::new(ErrorTypeDefaults::ArgumentsType, self))
91+
}
92+
8893
fn validate_dataclass_args<'a>(&'a self, class_name: &str) -> ValResult<JsonArgs<'a, 'data>> {
8994
match self {
9095
JsonValue::Object(object) => Ok(JsonArgs::new(None, Some(object))),
@@ -383,6 +388,11 @@ impl<'py> Input<'py> for str {
383388
Err(ValError::new(ErrorTypeDefaults::ArgumentsType, self))
384389
}
385390

391+
#[cfg_attr(has_coverage_attribute, coverage(off))]
392+
fn validate_args_v3(&self) -> ValResult<Never> {
393+
Err(ValError::new(ErrorTypeDefaults::ArgumentsType, self))
394+
}
395+
386396
#[cfg_attr(has_coverage_attribute, coverage(off))]
387397
fn validate_dataclass_args(&self, class_name: &str) -> ValResult<Never> {
388398
let class_name = class_name.to_string();

src/input/input_python.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@ impl<'py> Input<'py> for Bound<'py, PyAny> {
117117
}
118118
}
119119

120+
fn validate_args_v3(&self) -> ValResult<PyArgs<'py>> {
121+
if let Ok(args_kwargs) = self.extract::<ArgsKwargs>() {
122+
let args = args_kwargs.args.into_bound(self.py());
123+
let kwargs = args_kwargs.kwargs.map(|d| d.into_bound(self.py()));
124+
Ok(PyArgs::new(Some(args), kwargs))
125+
} else {
126+
Err(ValError::new(ErrorTypeDefaults::ArgumentsType, self))
127+
}
128+
}
129+
120130
fn validate_dataclass_args<'a>(&'a self, class_name: &str) -> ValResult<PyArgs<'py>> {
121131
if let Ok(dict) = self.downcast::<PyDict>() {
122132
Ok(PyArgs::new(None, Some(dict.clone())))

src/input/input_string.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ impl<'py> Input<'py> for StringMapping<'py> {
8989
Err(ValError::new(ErrorTypeDefaults::ArgumentsType, self))
9090
}
9191

92+
fn validate_args_v3(&self) -> ValResult<Self::Arguments<'_>> {
93+
// do we want to support this?
94+
Err(ValError::new(ErrorTypeDefaults::ArgumentsType, self))
95+
}
96+
9297
fn validate_dataclass_args<'a>(&'a self, _dataclass_name: &str) -> ValResult<StringMappingDict<'py>> {
9398
match self {
9499
StringMapping::String(_) => Err(ValError::new(ErrorTypeDefaults::ArgumentsType, self)),

0 commit comments

Comments
 (0)