Skip to content
Merged
44 changes: 36 additions & 8 deletions python/pydantic_core/core_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1954,7 +1954,7 @@ class NoInfoValidatorFunctionSchema(TypedDict):
class WithInfoValidatorFunctionSchema(TypedDict, total=False):
type: Required[Literal['with-info']]
function: Required[WithInfoValidatorFunction]
field_name: str
field_name: str # deprecated
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this TypedDict is only used for type hinting -- let me know if this is sufficient or if we should force a deprecation warning here as well somehow.



ValidationFunction = Union[NoInfoValidatorFunctionSchema, WithInfoValidatorFunctionSchema]
Expand Down Expand Up @@ -2042,7 +2042,7 @@ def fn(v: bytes, info: core_schema.ValidationInfo) -> str:
return v.decode() + 'world'

func_schema = core_schema.with_info_before_validator_function(
function=fn, schema=core_schema.str_schema(), field_name='a'
function=fn, schema=core_schema.str_schema()
)
schema = core_schema.typed_dict_schema({'a': core_schema.typed_dict_field(func_schema)})

Expand All @@ -2052,13 +2052,20 @@ def fn(v: bytes, info: core_schema.ValidationInfo) -> str:

Args:
function: The validator function to call
field_name: The name of the field
field_name: The name of the field this validator is applied to, if any (deprecated)
schema: The schema to validate the output of the validator function
ref: optional unique identifier of the schema, used to reference the schema in other places
json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type
metadata: Any other information you want to include with the schema, not used by pydantic-core
serialization: Custom serialization schema
"""
if field_name is not None:
warnings.warn(
'The `field_name` argument on `with_info_before_validator_function` is deprecated, it will be passed to the function through `ValidationState` instead.',
DeprecationWarning,
stacklevel=2,
)

return _dict_not_none(
type='function-before',
function=_dict_not_none(type='with-info', function=function, field_name=field_name),
Expand Down Expand Up @@ -2140,7 +2147,7 @@ def fn(v: str, info: core_schema.ValidationInfo) -> str:
return v + 'world'

func_schema = core_schema.with_info_after_validator_function(
function=fn, schema=core_schema.str_schema(), field_name='a'
function=fn, schema=core_schema.str_schema()
)
schema = core_schema.typed_dict_schema({'a': core_schema.typed_dict_field(func_schema)})

Expand All @@ -2151,11 +2158,18 @@ def fn(v: str, info: core_schema.ValidationInfo) -> str:
Args:
function: The validator function to call after the schema is validated
schema: The schema to validate before the validator function
field_name: The name of the field this validators is applied to, if any
field_name: The name of the field this validator is applied to, if any (deprecated)
ref: optional unique identifier of the schema, used to reference the schema in other places
metadata: Any other information you want to include with the schema, not used by pydantic-core
serialization: Custom serialization schema
"""
if field_name is not None:
warnings.warn(
'The `field_name` argument on `with_info_after_validator_function` is deprecated, it will be passed to the function through `ValidationState` instead.',
DeprecationWarning,
stacklevel=2,
)

return _dict_not_none(
type='function-after',
function=_dict_not_none(type='with-info', function=function, field_name=field_name),
Expand Down Expand Up @@ -2187,7 +2201,7 @@ class NoInfoWrapValidatorFunctionSchema(TypedDict):
class WithInfoWrapValidatorFunctionSchema(TypedDict, total=False):
type: Required[Literal['with-info']]
function: Required[WithInfoWrapValidatorFunction]
field_name: str
field_name: str # deprecated


WrapValidatorFunction = Union[NoInfoWrapValidatorFunctionSchema, WithInfoWrapValidatorFunctionSchema]
Expand Down Expand Up @@ -2287,12 +2301,19 @@ def fn(
Args:
function: The validator function to call
schema: The schema to validate the output of the validator function
field_name: The name of the field this validators is applied to, if any
field_name: The name of the field this validator is applied to, if any (deprecated)
json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type
ref: optional unique identifier of the schema, used to reference the schema in other places
metadata: Any other information you want to include with the schema, not used by pydantic-core
serialization: Custom serialization schema
"""
if field_name is not None:
warnings.warn(
'The `field_name` argument on `with_info_wrap_validator_function` is deprecated, it will be passed to the function through `ValidationState` instead.',
DeprecationWarning,
stacklevel=2,
)

return _dict_not_none(
type='function-wrap',
function=_dict_not_none(type='with-info', function=function, field_name=field_name),
Expand Down Expand Up @@ -2379,12 +2400,19 @@ def fn(v: str, info: core_schema.ValidationInfo) -> str:

Args:
function: The validator function to call
field_name: The name of the field this validators is applied to, if any
field_name: The name of the field this validator is applied to, if any (deprecated)
ref: optional unique identifier of the schema, used to reference the schema in other places
json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type
metadata: Any other information you want to include with the schema, not used by pydantic-core
serialization: Custom serialization schema
"""
if field_name is not None:
warnings.warn(
'The `field_name` argument on `with_info_plain_validator_function` is deprecated, it will be passed to the function through `ValidationState` instead.',
DeprecationWarning,
stacklevel=2,
)

return _dict_not_none(
type='function-plain',
function=_dict_not_none(type='with-info', function=function, field_name=field_name),
Expand Down
3 changes: 3 additions & 0 deletions src/validators/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ impl Validator for ArgumentsValidator {
}
}

let state =
&mut state.rebind_extra(|extra| extra.field_name = Some(PyString::new(py, parameter.name.as_str())));

match (pos_value, kw_value) {
(Some(_), Some((_, kw_value))) => {
errors.push(ValLineError::new_with_loc(
Expand Down
25 changes: 14 additions & 11 deletions src/validators/dataclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuild
struct Field {
kw_only: bool,
name: String,
py_name: Py<PyString>,
name_py: Py<PyString>,
init: bool,
init_only: bool,
lookup_key_collection: LookupKeyCollection,
Expand Down Expand Up @@ -72,8 +72,8 @@ impl BuildValidator for DataclassArgsValidator {
for field in fields_schema {
let field = field.downcast::<PyDict>()?;

let py_name: Bound<'_, PyString> = field.get_as_req(intern!(py, "name"))?;
let name: String = py_name.extract()?;
let name_py: Bound<'_, PyString> = field.get_as_req(intern!(py, "name"))?;
let name: String = name_py.extract()?;

let schema = field.get_as_req(intern!(py, "schema"))?;

Expand All @@ -99,7 +99,7 @@ impl BuildValidator for DataclassArgsValidator {
fields.push(Field {
kw_only,
name,
py_name: py_name.into(),
name_py: name_py.into(),
lookup_key_collection,
validator,
init: field.get_as(intern!(py, "init"))?.unwrap_or(true),
Expand Down Expand Up @@ -163,13 +163,13 @@ impl Validator for DataclassArgsValidator {

macro_rules! set_item {
($field:ident, $value:expr) => {{
let py_name = $field.py_name.bind(py);
let name_py = $field.name_py.bind(py);
if $field.init_only {
if let Some(ref mut init_only_args) = init_only_args {
init_only_args.push($value);
}
} else {
output_dict.set_item(py_name, $value)?;
output_dict.set_item(name_py, $value)?;
}
}};
}
Expand Down Expand Up @@ -214,6 +214,8 @@ impl Validator for DataclassArgsValidator {
}
let kw_value = kw_value.as_ref().map(|(path, value)| (path, value.borrow_input()));

let state = &mut state.rebind_extra(|extra| extra.field_name = Some(field.name_py.bind(py).clone()));

match (pos_value, kw_value) {
// found both positional and keyword arguments, error
(Some(_), Some((_, kw_value))) => {
Expand Down Expand Up @@ -404,11 +406,12 @@ impl Validator for DataclassArgsValidator {
}
}

match field.validator.validate(
py,
field_value,
&mut state.rebind_extra(|extra| extra.data = Some(data_dict.clone())),
) {
let state = &mut state.rebind_extra(|extra| {
extra.data = Some(data_dict.clone());
extra.field_name = Some(field.name_py.bind(py).clone());
});

match field.validator.validate(py, field_value, state) {
Ok(output) => ok(output),
Err(ValError::LineErrors(line_errors)) => {
let errors = line_errors
Expand Down
32 changes: 28 additions & 4 deletions src/validators/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,13 @@ impl FunctionBeforeValidator {
state: &'s mut ValidationState<'_, 'py>,
) -> ValResult<PyObject> {
let r = if self.info_arg {
let info = ValidationInfo::new(py, state.extra(), &self.config, self.field_name.clone());
let field_name = state
.extra()
.field_name
.clone()
.map(Bound::unbind)
.or_else(|| self.field_name.clone());
let info = ValidationInfo::new(py, state.extra(), &self.config, field_name);
self.func.call1(py, (input.to_object(py)?, info))
} else {
self.func.call1(py, (input.to_object(py)?,))
Expand Down Expand Up @@ -169,7 +175,13 @@ impl FunctionAfterValidator {
) -> ValResult<PyObject> {
let v = call(input, state)?;
let r = if self.info_arg {
let info = ValidationInfo::new(py, state.extra(), &self.config, self.field_name.clone());
let field_name = state
.extra()
.field_name
.clone()
.map(Bound::unbind)
.or_else(|| self.field_name.clone());
let info = ValidationInfo::new(py, state.extra(), &self.config, field_name);
self.func.call1(py, (v, info))
} else {
self.func.call1(py, (v,))
Expand Down Expand Up @@ -258,7 +270,13 @@ impl Validator for FunctionPlainValidator {
state: &mut ValidationState<'_, 'py>,
) -> ValResult<PyObject> {
let r = if self.info_arg {
let info = ValidationInfo::new(py, state.extra(), &self.config, self.field_name.clone());
let field_name = state
.extra()
.field_name
.clone()
.map(Bound::unbind)
.or_else(|| self.field_name.clone());
let info = ValidationInfo::new(py, state.extra(), &self.config, field_name);
self.func.call1(py, (input.to_object(py)?, info))
} else {
self.func.call1(py, (input.to_object(py)?,))
Expand Down Expand Up @@ -322,7 +340,13 @@ impl FunctionWrapValidator {
state: &mut ValidationState<'_, 'py>,
) -> ValResult<PyObject> {
let r = if self.info_arg {
let info = ValidationInfo::new(py, state.extra(), &self.config, self.field_name.clone());
let field_name = state
.extra()
.field_name
.clone()
.map(Bound::unbind)
.or_else(|| self.field_name.clone());
let info = ValidationInfo::new(py, state.extra(), &self.config, field_name);
self.func.call1(py, (input.to_object(py)?, handler, info))
} else {
self.func.call1(py, (input.to_object(py)?, handler))
Expand Down
2 changes: 2 additions & 0 deletions src/validators/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ impl InternalValidator {
data: self.data.as_ref().map(|data| data.bind(py).clone()),
strict: self.strict,
from_attributes: self.from_attributes,
field_name: Some(PyString::new(py, field_name)),
context: self.context.as_ref().map(|data| data.bind(py)),
self_instance: self.self_instance.as_ref().map(|data| data.bind(py)),
cache_str: self.cache_str,
Expand Down Expand Up @@ -313,6 +314,7 @@ impl InternalValidator {
data: self.data.as_ref().map(|data| data.bind(py).clone()),
strict: self.strict,
from_attributes: self.from_attributes,
field_name: None,
context: self.context.as_ref().map(|data| data.bind(py)),
self_instance: self.self_instance.as_ref().map(|data| data.bind(py)),
cache_str: self.cache_str,
Expand Down
6 changes: 6 additions & 0 deletions src/validators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ impl SchemaValidator {
data: None,
strict,
from_attributes,
field_name: Some(PyString::new(py, field_name)),
context,
self_instance: None,
cache_str: self.cache_str,
Expand All @@ -337,6 +338,7 @@ impl SchemaValidator {
data: None,
strict,
from_attributes: None,
field_name: None,
context,
self_instance: None,
cache_str: self.cache_str,
Expand Down Expand Up @@ -678,6 +680,8 @@ pub struct Extra<'a, 'py> {
pub from_attributes: Option<bool>,
/// context used in validator functions
pub context: Option<&'a Bound<'py, PyAny>>,
/// The name of the field being validated, if applicable
pub field_name: Option<Bound<'py, PyString>>,
/// This is an instance of the model or dataclass being validated, when validation is performed from `__init__`
self_instance: Option<&'a Bound<'py, PyAny>>,
/// Whether to use a cache of short strings to accelerate python string construction
Expand Down Expand Up @@ -705,6 +709,7 @@ impl<'a, 'py> Extra<'a, 'py> {
data: None,
strict,
from_attributes,
field_name: None,
context,
self_instance,
cache_str,
Expand All @@ -721,6 +726,7 @@ impl Extra<'_, '_> {
data: self.data.clone(),
strict: Some(true),
from_attributes: self.from_attributes,
field_name: self.field_name.clone(),
context: self.context,
self_instance: self.self_instance,
cache_str: self.cache_str,
Expand Down
14 changes: 11 additions & 3 deletions src/validators/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ impl Validator for ModelValidator {
field_name.to_string(),
))
} else {
let state = &mut state.rebind_extra(|extra| extra.field_name = Some(PyString::new(py, ROOT_FIELD)));
let output = self.validator.validate(py, field_value, state)?;

force_setattr(py, model, intern!(py, ROOT_FIELD), output)?;
Expand Down Expand Up @@ -255,9 +256,11 @@ impl ModelValidator {
// we need to set `self_instance` to None for nested validators as we don't want to operate on self_instance
// anymore
let state = &mut state.rebind_extra(|extra| extra.self_instance = None);
let output = self.validator.validate(py, input, state)?;

if self.root_model {
let state = &mut state.rebind_extra(|extra| extra.field_name = Some(PyString::new(py, ROOT_FIELD)));
let output = self.validator.validate(py, input, state)?;

let fields_set = if input.as_python().is_some_and(|py_input| py_input.is(&self.undefined)) {
PySet::empty(py)?
} else {
Expand All @@ -266,6 +269,8 @@ impl ModelValidator {
force_setattr(py, self_instance, intern!(py, DUNDER_FIELDS_SET_KEY), &fields_set)?;
force_setattr(py, self_instance, intern!(py, ROOT_FIELD), &output)?;
} else {
let output = self.validator.validate(py, input, state)?;

let (model_dict, model_extra, fields_set): (Bound<PyAny>, Bound<PyAny>, Bound<PyAny>) =
output.extract(py)?;
set_model_attrs(self_instance, &model_dict, &model_extra, &fields_set)?;
Expand Down Expand Up @@ -294,11 +299,12 @@ impl ModelValidator {
}
}

let output = self.validator.validate(py, input, state)?;

let instance = create_class(self.class.bind(py))?;

if self.root_model {
let state = &mut state.rebind_extra(|extra| extra.field_name = Some(PyString::new(py, ROOT_FIELD)));
let output = self.validator.validate(py, input, state)?;

let fields_set = if input.as_python().is_some_and(|py_input| py_input.is(&self.undefined)) {
PySet::empty(py)?
} else {
Expand All @@ -307,6 +313,8 @@ impl ModelValidator {
force_setattr(py, &instance, intern!(py, DUNDER_FIELDS_SET_KEY), &fields_set)?;
force_setattr(py, &instance, intern!(py, ROOT_FIELD), output)?;
} else {
let output = self.validator.validate(py, input, state)?;

let (model_dict, model_extra, val_fields_set): (Bound<PyAny>, Bound<PyAny>, Bound<PyAny>) =
output.extract(py)?;
let fields_set = existing_fields_set.unwrap_or(&val_fields_set);
Expand Down
6 changes: 6 additions & 0 deletions src/validators/model_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ impl Validator for ModelFieldsValidator {
// extra logic either way
used_keys.insert(lookup_path.first_key());
}

let state =
&mut state.rebind_extra(|extra| extra.field_name = Some(field.name_py.bind(py).clone()));

match field.validator.validate(py, value.borrow_input(), state) {
Ok(value) => {
model_dict.set_item(&field.name_py, value)?;
Expand Down Expand Up @@ -422,6 +426,8 @@ impl Validator for ModelFieldsValidator {
));
}

let state = &mut state.rebind_extra(|extra| extra.field_name = Some(field.name_py.bind(py).clone()));

prepare_result(field.validator.validate(py, field_value, state))?
} else {
// Handle extra (unknown) field
Expand Down
Loading
Loading