From feda72f6fc0f169c4abe86c8cecb9184e55fc5f1 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 21 Aug 2023 11:59:38 -0500 Subject: [PATCH 1/4] Allow customizing serialization of extras --- src/serializers/fields.rs | 13 ++++++-- src/serializers/type_serializers/dataclass.rs | 2 +- src/serializers/type_serializers/model.rs | 9 +++++- .../type_serializers/typed_dict.rs | 11 ++++++- src/validators/dataclass.rs | 25 +++++++++++++++- tests/serializers/test_dataclasses.py | 30 ++++++++++++++++++- tests/serializers/test_model.py | 26 +++++++++++++++- tests/serializers/test_typed_dict.py | 15 ++++++++++ 8 files changed, 123 insertions(+), 8 deletions(-) diff --git a/src/serializers/fields.rs b/src/serializers/fields.rs index c8bf67971..f48f8e0b9 100644 --- a/src/serializers/fields.rs +++ b/src/serializers/fields.rs @@ -94,6 +94,7 @@ pub struct GeneralFieldsSerializer { fields: AHashMap, computed_fields: Option, mode: FieldsMode, + extra_serializer: Option>, // isize because we look up filter via `.hash()` which returns an isize filter: SchemaFilter, required_fields: usize, @@ -103,12 +104,14 @@ impl GeneralFieldsSerializer { pub(super) fn new( fields: AHashMap, mode: FieldsMode, + extra_serializer: Option, computed_fields: Option, ) -> Self { let required_fields = fields.values().filter(|f| f.required).count(); Self { fields, mode, + extra_serializer: extra_serializer.map(Box::new), filter: SchemaFilter::default(), computed_fields, required_fields, @@ -205,7 +208,10 @@ impl TypeSerializer for GeneralFieldsSerializer { used_req_fields += 1; } } else if self.mode == FieldsMode::TypedDictAllow { - let value = infer_to_python(value, next_include, next_exclude, &extra)?; + let value = match &self.extra_serializer { + Some(serializer) => serializer.to_python(value, next_include, next_exclude, &extra)?, + None => infer_to_python(value, next_include, next_exclude, &extra)?, + }; output_dict.set_item(key, value)?; } else if extra.check == SerCheck::Strict { return Err(PydanticSerializationUnexpectedValue::new_err(None)); @@ -227,7 +233,10 @@ impl TypeSerializer for GeneralFieldsSerializer { continue; } if let Some((next_include, next_exclude)) = self.filter.key_filter(key, include, exclude)? { - let value = infer_to_python(value, next_include, next_exclude, &td_extra)?; + let value = match &self.extra_serializer { + Some(serializer) => serializer.to_python(value, next_include, next_exclude, extra)?, + None => infer_to_python(value, next_include, next_exclude, extra)?, + }; output_dict.set_item(key, value)?; } } diff --git a/src/serializers/type_serializers/dataclass.rs b/src/serializers/type_serializers/dataclass.rs index aa3a180da..787e267dd 100644 --- a/src/serializers/type_serializers/dataclass.rs +++ b/src/serializers/type_serializers/dataclass.rs @@ -55,7 +55,7 @@ impl BuildSerializer for DataclassArgsBuilder { let computed_fields = ComputedFields::new(schema, config, definitions)?; - Ok(GeneralFieldsSerializer::new(fields, fields_mode, computed_fields).into()) + Ok(GeneralFieldsSerializer::new(fields, fields_mode, None, computed_fields).into()) } } diff --git a/src/serializers/type_serializers/model.rs b/src/serializers/type_serializers/model.rs index 9d14dd16b..8c621fa52 100644 --- a/src/serializers/type_serializers/model.rs +++ b/src/serializers/type_serializers/model.rs @@ -11,6 +11,7 @@ use super::{ CombinedSerializer, ComputedFields, Extra, FieldsMode, GeneralFieldsSerializer, ObType, SerCheck, SerField, TypeSerializer, }; +use crate::build_tools::py_schema_err; use crate::build_tools::{py_schema_error_type, ExtraBehavior}; use crate::definitions::DefinitionsBuilder; use crate::serializers::errors::PydanticSerializationUnexpectedValue; @@ -38,6 +39,12 @@ impl BuildSerializer for ModelFieldsBuilder { let fields_dict: &PyDict = schema.get_as_req(intern!(py, "fields"))?; let mut fields: AHashMap = AHashMap::with_capacity(fields_dict.len()); + let extra_serializer = match (schema.get_item(intern!(py, "extra_validator")), &fields_mode) { + (Some(v), FieldsMode::ModelExtra) => Some(CombinedSerializer::build(v.extract()?, config, definitions)?), + (Some(_), _) => return py_schema_err!("extra_validator can only be used if extra_behavior=allow"), + (_, _) => None, + }; + for (key, value) in fields_dict { let key_py: &PyString = key.downcast()?; let key: String = key_py.extract()?; @@ -60,7 +67,7 @@ impl BuildSerializer for ModelFieldsBuilder { let computed_fields = ComputedFields::new(schema, config, definitions)?; - Ok(GeneralFieldsSerializer::new(fields, fields_mode, computed_fields).into()) + Ok(GeneralFieldsSerializer::new(fields, fields_mode, extra_serializer, computed_fields).into()) } } diff --git a/src/serializers/type_serializers/typed_dict.rs b/src/serializers/type_serializers/typed_dict.rs index 4d0cf4a3c..e3cfe87f1 100644 --- a/src/serializers/type_serializers/typed_dict.rs +++ b/src/serializers/type_serializers/typed_dict.rs @@ -4,6 +4,7 @@ use pyo3::types::{PyDict, PyString}; use ahash::AHashMap; +use crate::build_tools::py_schema_err; use crate::build_tools::{py_schema_error_type, schema_or_config, ExtraBehavior}; use crate::definitions::DefinitionsBuilder; use crate::tools::SchemaDict; @@ -34,6 +35,14 @@ impl BuildSerializer for TypedDictBuilder { let fields_dict: &PyDict = schema.get_as_req(intern!(py, "fields"))?; let mut fields: AHashMap = AHashMap::with_capacity(fields_dict.len()); + let extra_serializer = match (schema.get_item(intern!(py, "extra_validator")), &fields_mode) { + (Some(v), FieldsMode::TypedDictAllow) => { + Some(CombinedSerializer::build(v.extract()?, config, definitions)?) + } + (Some(_), _) => return py_schema_err!("extra_validator can only be used if extra_behavior=allow"), + (_, _) => None, + }; + for (key, value) in fields_dict { let key_py: &PyString = key.downcast()?; let key: String = key_py.extract()?; @@ -56,6 +65,6 @@ impl BuildSerializer for TypedDictBuilder { let computed_fields = ComputedFields::new(schema, config, definitions)?; - Ok(GeneralFieldsSerializer::new(fields, fields_mode, computed_fields).into()) + Ok(GeneralFieldsSerializer::new(fields, fields_mode, extra_serializer, computed_fields).into()) } } diff --git a/src/validators/dataclass.rs b/src/validators/dataclass.rs index b5f8dc6ba..d7b0ddb18 100644 --- a/src/validators/dataclass.rs +++ b/src/validators/dataclass.rs @@ -38,6 +38,7 @@ pub struct DataclassArgsValidator { dataclass_name: String, validator_name: String, extra_behavior: ExtraBehavior, + extra_validator: Option>, loc_by_alias: bool, } @@ -55,6 +56,12 @@ impl BuildValidator for DataclassArgsValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; + let extra_validator = match (schema.get_item(intern!(py, "extra_validator")), &extra_behavior) { + (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, definitions)?)), + (Some(_), _) => return py_schema_err!("extra_validator can only be used if extra_behavior=allow"), + (_, _) => None, + }; + let fields_schema: &PyList = schema.get_as_req(intern!(py, "fields"))?; let mut fields: Vec = Vec::with_capacity(fields_schema.len()); @@ -118,6 +125,7 @@ impl BuildValidator for DataclassArgsValidator { dataclass_name, validator_name, extra_behavior, + extra_validator, loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), } .into()) @@ -267,7 +275,22 @@ impl Validator for DataclassArgsValidator { } ExtraBehavior::Ignore => {} ExtraBehavior::Allow => { - output_dict.set_item(either_str.as_py_string(py), value)? + if let Some(ref validator) = self.extra_validator { + match validator.validate(py, value, state) { + Ok(value) => output_dict + .set_item(either_str.as_py_string(py), value)?, + Err(ValError::LineErrors(line_errors)) => { + for err in line_errors { + errors.push(err.with_outer_location( + raw_key.as_loc_item(), + )); + } + } + Err(err) => return Err(err), + } + } else { + output_dict.set_item(either_str.as_py_string(py), value)? + } } } } diff --git a/tests/serializers/test_dataclasses.py b/tests/serializers/test_dataclasses.py index 57b20c4b2..57aea2b6d 100644 --- a/tests/serializers/test_dataclasses.py +++ b/tests/serializers/test_dataclasses.py @@ -6,7 +6,7 @@ import pytest -from pydantic_core import SchemaSerializer, core_schema +from pydantic_core import SchemaSerializer, SchemaValidator, core_schema on_pypy = platform.python_implementation() == 'PyPy' # pypy doesn't seem to maintain order of `__dict__` @@ -164,3 +164,31 @@ class SubModel(Model): s = SchemaSerializer(schema) assert s.to_python(dc) == {'x': 1, 'x2': 2} assert s.to_json(dc) == b'{"x":1,"x2":2}' + + +@pytest.mark.xfail(reason='dataclasses do not serialize extras') +def test_extra_custom_serializer(): + @dataclasses.dataclass + class Model: + pass + + schema = core_schema.dataclass_schema( + Model, + core_schema.dataclass_args_schema( + 'Model', + [], + extra_behavior='allow', + # extra_validator=core_schema.any_schema( + # serialization=core_schema.plain_serializer_function_ser_schema( + # lambda v: v + ' bam!', + # ) + # ) + ), + [], + ) + s = SchemaSerializer(schema) + v = SchemaValidator(schema) + + m = v.validate_python({'extra': 'extra'}) + + assert s.to_python(m) == {'extra': 'extra bam!'} diff --git a/tests/serializers/test_model.py b/tests/serializers/test_model.py index e13cadb70..cd99bd8a8 100644 --- a/tests/serializers/test_model.py +++ b/tests/serializers/test_model.py @@ -2,7 +2,7 @@ import json import platform from random import randint -from typing import Any, ClassVar +from typing import Any, ClassVar, Dict try: from functools import cached_property @@ -915,3 +915,27 @@ class InnerModel: s_repr = plain_repr(s) assert 'has_extra:true,root_model:false,name:"InnerModel"' in s_repr assert 'has_extra:false,root_model:false,name:"OuterModel"' in s_repr + + +def test_extra_custom_serializer(): + class Model: + __slots__ = ('__pydantic_extra__', '__dict__') + __pydantic_extra__: Dict[str, Any] + + schema = core_schema.model_schema( + Model, + core_schema.model_fields_schema( + {}, + extra_behavior='allow', + extra_validator=core_schema.any_schema( + serialization=core_schema.plain_serializer_function_ser_schema(lambda v: v + ' bam!') + ), + ), + extra_behavior='allow', + ) + s = SchemaSerializer(schema) + + m = Model() + m.__pydantic_extra__ = {'extra': 'extra'} + + assert s.to_python(m) == {'extra': 'extra bam!'} diff --git a/tests/serializers/test_typed_dict.py b/tests/serializers/test_typed_dict.py index 9bc3c64a9..408d47dde 100644 --- a/tests/serializers/test_typed_dict.py +++ b/tests/serializers/test_typed_dict.py @@ -318,3 +318,18 @@ def ser_x(data: Model, v: Any, serializer: core_schema.SerializerFunctionWrapHan ) ) assert json.loads(s.to_json(Model(x=1000))) == {'x': '1_000'} + + +def test_extra_custom_serializer(): + schema = core_schema.typed_dict_schema( + {}, + extra_behavior='allow', + extra_validator=core_schema.any_schema( + serialization=core_schema.plain_serializer_function_ser_schema(lambda v: v + ' bam!') + ), + ) + s = SchemaSerializer(schema) + + m = {'extra': 'extra'} + + assert s.to_python(m) == {'extra': 'extra bam!'} From 785addeca002306eab725438ef14995ad90883d2 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 23 Aug 2023 10:05:52 -0500 Subject: [PATCH 2/4] Rename extra_validator -> extra_serializer --- python/pydantic_core/core_schema.py | 16 ++++++++-------- src/serializers/type_serializers/model.rs | 4 ++-- .../type_serializers/typed_dict.rs | 4 ++-- src/validators/dataclass.rs | 10 +++++----- src/validators/model_fields.rs | 19 ++++++++----------- src/validators/tuple.rs | 18 +++++++++--------- src/validators/typed_dict.rs | 17 +++++++---------- tests/serializers/test_dataclasses.py | 2 +- tests/serializers/test_model.py | 2 +- tests/serializers/test_typed_dict.py | 2 +- tests/test_json.py | 2 +- tests/validators/test_model_fields.py | 16 ++++++++-------- tests/validators/test_typed_dict.py | 16 ++++++++-------- 13 files changed, 61 insertions(+), 67 deletions(-) diff --git a/python/pydantic_core/core_schema.py b/python/pydantic_core/core_schema.py index 725d41eee..01f46de9b 100644 --- a/python/pydantic_core/core_schema.py +++ b/python/pydantic_core/core_schema.py @@ -2829,7 +2829,7 @@ class TypedDictSchema(TypedDict, total=False): fields: Required[Dict[str, TypedDictField]] computed_fields: List[ComputedField] strict: bool - extra_validator: CoreSchema + extra_schema: CoreSchema # all these values can be set via config, equivalent fields have `typed_dict_` prefix extra_behavior: ExtraBehavior total: bool # default: True @@ -2845,7 +2845,7 @@ def typed_dict_schema( *, computed_fields: list[ComputedField] | None = None, strict: bool | None = None, - extra_validator: CoreSchema | None = None, + extra_schema: CoreSchema | None = None, extra_behavior: ExtraBehavior | None = None, total: bool | None = None, populate_by_name: bool | None = None, @@ -2871,7 +2871,7 @@ def typed_dict_schema( fields: The fields to use for the typed dict computed_fields: Computed fields to use when serializing the model, only applies when directly inside a model strict: Whether the typed dict is strict - extra_validator: The extra validator to use for the typed dict + extra_schema: The extra validator to use for the typed dict 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 extra_behavior: The extra behavior to use for the typed dict @@ -2884,7 +2884,7 @@ def typed_dict_schema( fields=fields, computed_fields=computed_fields, strict=strict, - extra_validator=extra_validator, + extra_schema=extra_schema, extra_behavior=extra_behavior, total=total, populate_by_name=populate_by_name, @@ -2948,7 +2948,7 @@ class ModelFieldsSchema(TypedDict, total=False): model_name: str computed_fields: List[ComputedField] strict: bool - extra_validator: CoreSchema + extra_schema: CoreSchema # all these values can be set via config, equivalent fields have `typed_dict_` prefix extra_behavior: ExtraBehavior populate_by_name: bool # replaces `allow_population_by_field_name` in pydantic v1 @@ -2964,7 +2964,7 @@ def model_fields_schema( model_name: str | None = None, computed_fields: list[ComputedField] | None = None, strict: bool | None = None, - extra_validator: CoreSchema | None = None, + extra_schema: CoreSchema | None = None, extra_behavior: ExtraBehavior | None = None, populate_by_name: bool | None = None, from_attributes: bool | None = None, @@ -2991,7 +2991,7 @@ def model_fields_schema( model_name: The name of the model, used for error messages, defaults to "Model" computed_fields: Computed fields to use when serializing the model, only applies when directly inside a model strict: Whether the typed dict is strict - extra_validator: The extra validator to use for the typed dict + extra_schema: The extra validator to use for the typed dict 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 extra_behavior: The extra behavior to use for the typed dict @@ -3005,7 +3005,7 @@ def model_fields_schema( model_name=model_name, computed_fields=computed_fields, strict=strict, - extra_validator=extra_validator, + extra_schema=extra_schema, extra_behavior=extra_behavior, populate_by_name=populate_by_name, from_attributes=from_attributes, diff --git a/src/serializers/type_serializers/model.rs b/src/serializers/type_serializers/model.rs index 8c621fa52..65dceea0a 100644 --- a/src/serializers/type_serializers/model.rs +++ b/src/serializers/type_serializers/model.rs @@ -39,9 +39,9 @@ impl BuildSerializer for ModelFieldsBuilder { let fields_dict: &PyDict = schema.get_as_req(intern!(py, "fields"))?; let mut fields: AHashMap = AHashMap::with_capacity(fields_dict.len()); - let extra_serializer = match (schema.get_item(intern!(py, "extra_validator")), &fields_mode) { + let extra_serializer = match (schema.get_item(intern!(py, "extra_schema")), &fields_mode) { (Some(v), FieldsMode::ModelExtra) => Some(CombinedSerializer::build(v.extract()?, config, definitions)?), - (Some(_), _) => return py_schema_err!("extra_validator can only be used if extra_behavior=allow"), + (Some(_), _) => return py_schema_err!("extra_schema can only be used if extra_behavior=allow"), (_, _) => None, }; diff --git a/src/serializers/type_serializers/typed_dict.rs b/src/serializers/type_serializers/typed_dict.rs index e3cfe87f1..8a4765b04 100644 --- a/src/serializers/type_serializers/typed_dict.rs +++ b/src/serializers/type_serializers/typed_dict.rs @@ -35,11 +35,11 @@ impl BuildSerializer for TypedDictBuilder { let fields_dict: &PyDict = schema.get_as_req(intern!(py, "fields"))?; let mut fields: AHashMap = AHashMap::with_capacity(fields_dict.len()); - let extra_serializer = match (schema.get_item(intern!(py, "extra_validator")), &fields_mode) { + let extra_serializer = match (schema.get_item(intern!(py, "extra_schema")), &fields_mode) { (Some(v), FieldsMode::TypedDictAllow) => { Some(CombinedSerializer::build(v.extract()?, config, definitions)?) } - (Some(_), _) => return py_schema_err!("extra_validator can only be used if extra_behavior=allow"), + (Some(_), _) => return py_schema_err!("extra_schema can only be used if extra_behavior=allow"), (_, _) => None, }; diff --git a/src/validators/dataclass.rs b/src/validators/dataclass.rs index d7b0ddb18..1edf73942 100644 --- a/src/validators/dataclass.rs +++ b/src/validators/dataclass.rs @@ -38,7 +38,7 @@ pub struct DataclassArgsValidator { dataclass_name: String, validator_name: String, extra_behavior: ExtraBehavior, - extra_validator: Option>, + extra_schema: Option>, loc_by_alias: bool, } @@ -56,9 +56,9 @@ impl BuildValidator for DataclassArgsValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; - let extra_validator = match (schema.get_item(intern!(py, "extra_validator")), &extra_behavior) { + let extra_schema = match (schema.get_item(intern!(py, "extra_schema")), &extra_behavior) { (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, definitions)?)), - (Some(_), _) => return py_schema_err!("extra_validator can only be used if extra_behavior=allow"), + (Some(_), _) => return py_schema_err!("extra_schema can only be used if extra_behavior=allow"), (_, _) => None, }; @@ -125,7 +125,7 @@ impl BuildValidator for DataclassArgsValidator { dataclass_name, validator_name, extra_behavior, - extra_validator, + extra_schema, loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), } .into()) @@ -275,7 +275,7 @@ impl Validator for DataclassArgsValidator { } ExtraBehavior::Ignore => {} ExtraBehavior::Allow => { - if let Some(ref validator) = self.extra_validator { + if let Some(ref validator) = self.extra_schema { match validator.validate(py, value, state) { Ok(value) => output_dict .set_item(either_str.as_py_string(py), value)?, diff --git a/src/validators/model_fields.rs b/src/validators/model_fields.rs index 131118cdb..a38fc46ec 100644 --- a/src/validators/model_fields.rs +++ b/src/validators/model_fields.rs @@ -36,7 +36,7 @@ pub struct ModelFieldsValidator { fields: Vec, model_name: String, extra_behavior: ExtraBehavior, - extra_validator: Option>, + extra_schema: Option>, strict: bool, from_attributes: bool, loc_by_alias: bool, @@ -58,9 +58,9 @@ impl BuildValidator for ModelFieldsValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; - let extra_validator = match (schema.get_item(intern!(py, "extra_validator")), &extra_behavior) { + let extra_schema = match (schema.get_item(intern!(py, "extra_schema")), &extra_behavior) { (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, definitions)?)), - (Some(_), _) => return py_schema_err!("extra_validator can only be used if extra_behavior=allow"), + (Some(_), _) => return py_schema_err!("extra_schema can only be used if extra_behavior=allow"), (_, _) => None, }; let model_name: String = schema @@ -102,7 +102,7 @@ impl BuildValidator for ModelFieldsValidator { fields, model_name, extra_behavior, - extra_validator, + extra_schema, strict, from_attributes, loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), @@ -111,10 +111,7 @@ impl BuildValidator for ModelFieldsValidator { } } -impl_py_gc_traverse!(ModelFieldsValidator { - fields, - extra_validator -}); +impl_py_gc_traverse!(ModelFieldsValidator { fields, extra_schema }); impl Validator for ModelFieldsValidator { fn validate<'data>( @@ -265,7 +262,7 @@ impl Validator for ModelFieldsValidator { ExtraBehavior::Ignore => {} ExtraBehavior::Allow => { let py_key = either_str.as_py_string(py); - if let Some(ref validator) = self.extra_validator { + if let Some(ref validator) = self.extra_schema { match validator.validate(py, value, state) { Ok(value) => { model_extra_dict.set_item(py_key, value)?; @@ -373,7 +370,7 @@ impl Validator for ModelFieldsValidator { // For models / typed dicts we forbid assigning extra attributes // unless the user explicitly set extra_behavior to 'allow' match self.extra_behavior { - ExtraBehavior::Allow => match self.extra_validator { + ExtraBehavior::Allow => match self.extra_schema { Some(ref validator) => prepare_result( state.with_new_extra(new_extra, |state| validator.validate(py, field_value, state)), ), @@ -430,7 +427,7 @@ impl Validator for ModelFieldsValidator { self.fields .iter_mut() .try_for_each(|f| f.validator.complete(definitions))?; - match &mut self.extra_validator { + match &mut self.extra_schema { Some(v) => v.complete(definitions), None => Ok(()), } diff --git a/src/validators/tuple.rs b/src/validators/tuple.rs index 1a3033d48..fd2b57f90 100644 --- a/src/validators/tuple.rs +++ b/src/validators/tuple.rs @@ -91,7 +91,7 @@ impl Validator for TupleVariableValidator { pub struct TuplePositionalValidator { strict: bool, items_validators: Vec, - extra_validator: Option>, + extra_schema: Option>, name: String, } @@ -117,7 +117,7 @@ impl BuildValidator for TuplePositionalValidator { Ok(Self { strict: is_strict(schema, config)?, items_validators: validators, - extra_validator: match schema.get_item(intern!(py, "extra_schema")) { + extra_schema: match schema.get_item(intern!(py, "extra_schema")) { Some(v) => Some(Box::new(build_validator(v, config, definitions)?)), None => None, }, @@ -134,7 +134,7 @@ fn validate_tuple_positional<'s, 'data, T: Iterator>, state: &mut ValidationState, output: &mut Vec, errors: &mut Vec>, - extra_validator: &Option>, + extra_schema: &Option>, items_validators: &[CombinedValidator], collection_iter: &mut T, collection_len: Option, @@ -160,8 +160,8 @@ fn validate_tuple_positional<'s, 'data, T: Iterator>, } for (index, result) in collection_iter.enumerate() { let item = result?; - match extra_validator { - Some(ref extra_validator) => match extra_validator.validate(py, item, state) { + match extra_schema { + Some(ref extra_schema) => match extra_schema.validate(py, item, state) { Ok(item) => output.push(item), Err(ValError::LineErrors(line_errors)) => { errors.extend( @@ -193,7 +193,7 @@ fn validate_tuple_positional<'s, 'data, T: Iterator>, impl_py_gc_traverse!(TuplePositionalValidator { items_validators, - extra_validator + extra_schema }); impl Validator for TuplePositionalValidator { @@ -218,7 +218,7 @@ impl Validator for TuplePositionalValidator { state, &mut output, &mut errors, - &self.extra_validator, + &self.extra_schema, &self.items_validators, &mut $collection_iter, collection_len, @@ -252,7 +252,7 @@ impl Validator for TuplePositionalValidator { .any(|v| v.different_strict_behavior(definitions, true)) { true - } else if let Some(ref v) = self.extra_validator { + } else if let Some(ref v) = self.extra_schema { v.different_strict_behavior(definitions, true) } else { false @@ -270,7 +270,7 @@ impl Validator for TuplePositionalValidator { self.items_validators .iter_mut() .try_for_each(|v| v.complete(definitions))?; - match &mut self.extra_validator { + match &mut self.extra_schema { Some(v) => v.complete(definitions), None => Ok(()), } diff --git a/src/validators/typed_dict.rs b/src/validators/typed_dict.rs index 69fb7f641..f2b4ccc9f 100644 --- a/src/validators/typed_dict.rs +++ b/src/validators/typed_dict.rs @@ -35,7 +35,7 @@ impl_py_gc_traverse!(TypedDictField { validator }); pub struct TypedDictValidator { fields: Vec, extra_behavior: ExtraBehavior, - extra_validator: Option>, + extra_schema: Option>, strict: bool, loc_by_alias: bool, } @@ -61,9 +61,9 @@ impl BuildValidator for TypedDictValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; - let extra_validator = match (schema.get_item(intern!(py, "extra_validator")), &extra_behavior) { + let extra_schema = match (schema.get_item(intern!(py, "extra_schema")), &extra_behavior) { (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, definitions)?)), - (Some(_), _) => return py_schema_err!("extra_validator can only be used if extra_behavior=allow"), + (Some(_), _) => return py_schema_err!("extra_schema can only be used if extra_behavior=allow"), (_, _) => None, }; @@ -129,7 +129,7 @@ impl BuildValidator for TypedDictValidator { Ok(Self { fields, extra_behavior, - extra_validator, + extra_schema, strict, loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), } @@ -137,10 +137,7 @@ impl BuildValidator for TypedDictValidator { } } -impl_py_gc_traverse!(TypedDictValidator { - fields, - extra_validator -}); +impl_py_gc_traverse!(TypedDictValidator { fields, extra_schema }); impl Validator for TypedDictValidator { fn validate<'data>( @@ -261,7 +258,7 @@ impl Validator for TypedDictValidator { ExtraBehavior::Ignore => {} ExtraBehavior::Allow => { let py_key = either_str.as_py_string(py); - if let Some(ref validator) = self.extra_validator { + if let Some(ref validator) = self.extra_schema { match validator.validate(py, value, state) { Ok(value) => { output_dict.set_item(py_key, value)?; @@ -314,7 +311,7 @@ impl Validator for TypedDictValidator { self.fields .iter_mut() .try_for_each(|f| f.validator.complete(definitions))?; - match &mut self.extra_validator { + match &mut self.extra_schema { Some(v) => v.complete(definitions), None => Ok(()), } diff --git a/tests/serializers/test_dataclasses.py b/tests/serializers/test_dataclasses.py index 57aea2b6d..448e04d9b 100644 --- a/tests/serializers/test_dataclasses.py +++ b/tests/serializers/test_dataclasses.py @@ -178,7 +178,7 @@ class Model: 'Model', [], extra_behavior='allow', - # extra_validator=core_schema.any_schema( + # extra_schema=core_schema.any_schema( # serialization=core_schema.plain_serializer_function_ser_schema( # lambda v: v + ' bam!', # ) diff --git a/tests/serializers/test_model.py b/tests/serializers/test_model.py index cd99bd8a8..dbf5172a9 100644 --- a/tests/serializers/test_model.py +++ b/tests/serializers/test_model.py @@ -927,7 +927,7 @@ class Model: core_schema.model_fields_schema( {}, extra_behavior='allow', - extra_validator=core_schema.any_schema( + extra_schema=core_schema.any_schema( serialization=core_schema.plain_serializer_function_ser_schema(lambda v: v + ' bam!') ), ), diff --git a/tests/serializers/test_typed_dict.py b/tests/serializers/test_typed_dict.py index 408d47dde..4bb66e62c 100644 --- a/tests/serializers/test_typed_dict.py +++ b/tests/serializers/test_typed_dict.py @@ -324,7 +324,7 @@ def test_extra_custom_serializer(): schema = core_schema.typed_dict_schema( {}, extra_behavior='allow', - extra_validator=core_schema.any_schema( + extra_schema=core_schema.any_schema( serialization=core_schema.plain_serializer_function_ser_schema(lambda v: v + ' bam!') ), ) diff --git a/tests/test_json.py b/tests/test_json.py index c29c3ba67..45d811639 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -142,7 +142,7 @@ def test_error_loc(): 'fields': { 'field_a': {'type': 'typed-dict-field', 'schema': {'type': 'list', 'items_schema': {'type': 'int'}}} }, - 'extra_validator': {'type': 'int'}, + 'extra_schema': {'type': 'int'}, 'extra_behavior': 'allow', } ) diff --git a/tests/validators/test_model_fields.py b/tests/validators/test_model_fields.py index 61d0c779b..3f76b0da1 100644 --- a/tests/validators/test_model_fields.py +++ b/tests/validators/test_model_fields.py @@ -210,9 +210,9 @@ def test_forbid_extra(): def test_allow_extra_invalid(): - with pytest.raises(SchemaError, match='extra_validator can only be used if extra_behavior=allow'): + with pytest.raises(SchemaError, match='extra_schema can only be used if extra_behavior=allow'): SchemaValidator( - {'type': 'model-fields', 'fields': {}, 'extra_validator': {'type': 'int'}, 'extra_behavior': 'ignore'} + {'type': 'model-fields', 'fields': {}, 'extra_schema': {'type': 'int'}, 'extra_behavior': 'ignore'} ) @@ -358,7 +358,7 @@ def test_validate_assignment_allow_extra_validate(): { 'type': 'model-fields', 'fields': {'field_a': {'type': 'model-field', 'schema': {'type': 'str'}}}, - 'extra_validator': {'type': 'int'}, + 'extra_schema': {'type': 'int'}, 'extra_behavior': 'allow', } ) @@ -1659,19 +1659,19 @@ def test_frozen_field(): ], ) @pytest.mark.parametrize( - 'extra_validator_kw, expected_extra_value', - [({}, '123'), ({'extra_validator': None}, '123'), ({'extra_validator': core_schema.int_schema()}, 123)], - ids=['extra_validator=unset', 'extra_validator=None', 'extra_validator=int'], + 'extra_schema_kw, expected_extra_value', + [({}, '123'), ({'extra_schema': None}, '123'), ({'extra_schema': core_schema.int_schema()}, 123)], + ids=['extra_schema=unset', 'extra_schema=None', 'extra_schema=int'], ) def test_extra_behavior_allow( config: Union[core_schema.CoreConfig, None], schema_extra_behavior_kw: Dict[str, Any], - extra_validator_kw: Dict[str, Any], + extra_schema_kw: Dict[str, Any], expected_extra_value: Any, ): v = SchemaValidator( core_schema.model_fields_schema( - {'f': core_schema.model_field(core_schema.str_schema())}, **schema_extra_behavior_kw, **extra_validator_kw + {'f': core_schema.model_field(core_schema.str_schema())}, **schema_extra_behavior_kw, **extra_schema_kw ), config=config, ) diff --git a/tests/validators/test_typed_dict.py b/tests/validators/test_typed_dict.py index b073ad88d..bced2beb5 100644 --- a/tests/validators/test_typed_dict.py +++ b/tests/validators/test_typed_dict.py @@ -186,9 +186,9 @@ def test_forbid_extra(): def test_allow_extra_invalid(): - with pytest.raises(SchemaError, match='extra_validator can only be used if extra_behavior=allow'): + with pytest.raises(SchemaError, match='extra_schema can only be used if extra_behavior=allow'): SchemaValidator( - {'type': 'typed-dict', 'fields': {}, 'extra_validator': {'type': 'int'}, 'extra_behavior': 'ignore'} + {'type': 'typed-dict', 'fields': {}, 'extra_schema': {'type': 'int'}, 'extra_behavior': 'ignore'} ) @@ -1089,21 +1089,21 @@ def wrap_function(input_value, validator, info): ], ) @pytest.mark.parametrize( - 'extra_validator_kw, expected_extra_value', - [({}, '123'), ({'extra_validator': None}, '123'), ({'extra_validator': core_schema.int_schema()}, 123)], - ids=['extra_validator=unset', 'extra_validator=None', 'extra_validator=int'], + 'extra_schema_kw, expected_extra_value', + [({}, '123'), ({'extra_schema': None}, '123'), ({'extra_schema': core_schema.int_schema()}, 123)], + ids=['extra_schema=unset', 'extra_schema=None', 'extra_schema=int'], ) def test_extra_behavior_allow( config: Union[core_schema.CoreConfig, None], schema_extra_behavior_kw: Dict[str, Any], - extra_validator_kw: Dict[str, Any], + extra_schema_kw: Dict[str, Any], expected_extra_value: Any, ): v = SchemaValidator( core_schema.typed_dict_schema( {'f': core_schema.typed_dict_field(core_schema.str_schema())}, **schema_extra_behavior_kw, - **extra_validator_kw, + **extra_schema_kw, config=config, ) ) @@ -1173,7 +1173,7 @@ def validate(v, info): schema = core_schema.general_plain_validator_function(validate) schema = core_schema.typed_dict_schema( - {'f': core_schema.typed_dict_field(schema)}, extra_behavior='allow', extra_validator=schema + {'f': core_schema.typed_dict_field(schema)}, extra_behavior='allow', extra_schema=schema ) # If any of the Rust validators don't implement traversal properly, From e75353285365296fce533235a3a635f90ffbfed7 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 23 Aug 2023 10:26:48 -0500 Subject: [PATCH 3/4] extra_schema -> extras_schema --- python/pydantic_core/core_schema.py | 24 +++++++++---------- src/serializers/type_serializers/model.rs | 4 ++-- src/serializers/type_serializers/tuple.rs | 4 ++-- .../type_serializers/typed_dict.rs | 4 ++-- src/validators/dataclass.rs | 10 ++++---- src/validators/model_fields.rs | 16 ++++++------- src/validators/tuple.rs | 18 +++++++------- src/validators/typed_dict.rs | 14 +++++------ tests/benchmarks/test_micro_benchmarks.py | 2 +- tests/serializers/test_dataclasses.py | 2 +- tests/serializers/test_list_tuple.py | 4 ++-- tests/serializers/test_model.py | 2 +- tests/serializers/test_typed_dict.py | 2 +- tests/test_json.py | 2 +- tests/validators/test_model_fields.py | 16 ++++++------- tests/validators/test_tuple.py | 6 ++--- tests/validators/test_typed_dict.py | 16 ++++++------- tests/validators/test_with_default.py | 2 +- 18 files changed, 74 insertions(+), 74 deletions(-) diff --git a/python/pydantic_core/core_schema.py b/python/pydantic_core/core_schema.py index 01f46de9b..74442b44c 100644 --- a/python/pydantic_core/core_schema.py +++ b/python/pydantic_core/core_schema.py @@ -1366,7 +1366,7 @@ def list_schema( class TuplePositionalSchema(TypedDict, total=False): type: Required[Literal['tuple-positional']] items_schema: Required[List[CoreSchema]] - extra_schema: CoreSchema + extras_schema: CoreSchema strict: bool ref: str metadata: Any @@ -1376,7 +1376,7 @@ class TuplePositionalSchema(TypedDict, total=False): def tuple_positional_schema( items_schema: list[CoreSchema], *, - extra_schema: CoreSchema | None = None, + extras_schema: CoreSchema | None = None, strict: bool | None = None, ref: str | None = None, metadata: Any = None, @@ -1397,7 +1397,7 @@ def tuple_positional_schema( Args: items_schema: The value must be a tuple with items that match these schemas - extra_schema: The value must be a tuple with items that match this schema + extras_schema: The value must be a tuple with items that match this schema This was inspired by JSON schema's `prefixItems` and `items` fields. In python's `typing.Tuple`, you can't specify a type for "extra" items -- they must all be the same type if the length is variable. So this field won't be set from a `typing.Tuple` annotation on a pydantic model. @@ -1409,7 +1409,7 @@ def tuple_positional_schema( return _dict_not_none( type='tuple-positional', items_schema=items_schema, - extra_schema=extra_schema, + extras_schema=extras_schema, strict=strict, ref=ref, metadata=metadata, @@ -2829,7 +2829,7 @@ class TypedDictSchema(TypedDict, total=False): fields: Required[Dict[str, TypedDictField]] computed_fields: List[ComputedField] strict: bool - extra_schema: CoreSchema + extras_schema: CoreSchema # all these values can be set via config, equivalent fields have `typed_dict_` prefix extra_behavior: ExtraBehavior total: bool # default: True @@ -2845,7 +2845,7 @@ def typed_dict_schema( *, computed_fields: list[ComputedField] | None = None, strict: bool | None = None, - extra_schema: CoreSchema | None = None, + extras_schema: CoreSchema | None = None, extra_behavior: ExtraBehavior | None = None, total: bool | None = None, populate_by_name: bool | None = None, @@ -2871,7 +2871,7 @@ def typed_dict_schema( fields: The fields to use for the typed dict computed_fields: Computed fields to use when serializing the model, only applies when directly inside a model strict: Whether the typed dict is strict - extra_schema: The extra validator to use for the typed dict + extras_schema: The extra validator to use for the typed dict 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 extra_behavior: The extra behavior to use for the typed dict @@ -2884,7 +2884,7 @@ def typed_dict_schema( fields=fields, computed_fields=computed_fields, strict=strict, - extra_schema=extra_schema, + extras_schema=extras_schema, extra_behavior=extra_behavior, total=total, populate_by_name=populate_by_name, @@ -2948,7 +2948,7 @@ class ModelFieldsSchema(TypedDict, total=False): model_name: str computed_fields: List[ComputedField] strict: bool - extra_schema: CoreSchema + extras_schema: CoreSchema # all these values can be set via config, equivalent fields have `typed_dict_` prefix extra_behavior: ExtraBehavior populate_by_name: bool # replaces `allow_population_by_field_name` in pydantic v1 @@ -2964,7 +2964,7 @@ def model_fields_schema( model_name: str | None = None, computed_fields: list[ComputedField] | None = None, strict: bool | None = None, - extra_schema: CoreSchema | None = None, + extras_schema: CoreSchema | None = None, extra_behavior: ExtraBehavior | None = None, populate_by_name: bool | None = None, from_attributes: bool | None = None, @@ -2991,7 +2991,7 @@ def model_fields_schema( model_name: The name of the model, used for error messages, defaults to "Model" computed_fields: Computed fields to use when serializing the model, only applies when directly inside a model strict: Whether the typed dict is strict - extra_schema: The extra validator to use for the typed dict + extras_schema: The extra validator to use for the typed dict 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 extra_behavior: The extra behavior to use for the typed dict @@ -3005,7 +3005,7 @@ def model_fields_schema( model_name=model_name, computed_fields=computed_fields, strict=strict, - extra_schema=extra_schema, + extras_schema=extras_schema, extra_behavior=extra_behavior, populate_by_name=populate_by_name, from_attributes=from_attributes, diff --git a/src/serializers/type_serializers/model.rs b/src/serializers/type_serializers/model.rs index 65dceea0a..8a2eeb4e1 100644 --- a/src/serializers/type_serializers/model.rs +++ b/src/serializers/type_serializers/model.rs @@ -39,9 +39,9 @@ impl BuildSerializer for ModelFieldsBuilder { let fields_dict: &PyDict = schema.get_as_req(intern!(py, "fields"))?; let mut fields: AHashMap = AHashMap::with_capacity(fields_dict.len()); - let extra_serializer = match (schema.get_item(intern!(py, "extra_schema")), &fields_mode) { + let extra_serializer = match (schema.get_item(intern!(py, "extras_schema")), &fields_mode) { (Some(v), FieldsMode::ModelExtra) => Some(CombinedSerializer::build(v.extract()?, config, definitions)?), - (Some(_), _) => return py_schema_err!("extra_schema can only be used if extra_behavior=allow"), + (Some(_), _) => return py_schema_err!("extras_schema can only be used if extra_behavior=allow"), (_, _) => None, }; diff --git a/src/serializers/type_serializers/tuple.rs b/src/serializers/type_serializers/tuple.rs index 46d7ef775..00c61e250 100644 --- a/src/serializers/type_serializers/tuple.rs +++ b/src/serializers/type_serializers/tuple.rs @@ -159,8 +159,8 @@ impl BuildSerializer for TuplePositionalSerializer { let py = schema.py(); let items: &PyList = schema.get_as_req(intern!(py, "items_schema"))?; - let extra_serializer = match schema.get_as::<&PyDict>(intern!(py, "extra_schema"))? { - Some(extra_schema) => CombinedSerializer::build(extra_schema, config, definitions)?, + let extra_serializer = match schema.get_as::<&PyDict>(intern!(py, "extras_schema"))? { + Some(extras_schema) => CombinedSerializer::build(extras_schema, config, definitions)?, None => AnySerializer::build(schema, config, definitions)?, }; let items_serializers: Vec = items diff --git a/src/serializers/type_serializers/typed_dict.rs b/src/serializers/type_serializers/typed_dict.rs index 8a4765b04..5967738ae 100644 --- a/src/serializers/type_serializers/typed_dict.rs +++ b/src/serializers/type_serializers/typed_dict.rs @@ -35,11 +35,11 @@ impl BuildSerializer for TypedDictBuilder { let fields_dict: &PyDict = schema.get_as_req(intern!(py, "fields"))?; let mut fields: AHashMap = AHashMap::with_capacity(fields_dict.len()); - let extra_serializer = match (schema.get_item(intern!(py, "extra_schema")), &fields_mode) { + let extra_serializer = match (schema.get_item(intern!(py, "extras_schema")), &fields_mode) { (Some(v), FieldsMode::TypedDictAllow) => { Some(CombinedSerializer::build(v.extract()?, config, definitions)?) } - (Some(_), _) => return py_schema_err!("extra_schema can only be used if extra_behavior=allow"), + (Some(_), _) => return py_schema_err!("extras_schema can only be used if extra_behavior=allow"), (_, _) => None, }; diff --git a/src/validators/dataclass.rs b/src/validators/dataclass.rs index 1edf73942..c6999d4ec 100644 --- a/src/validators/dataclass.rs +++ b/src/validators/dataclass.rs @@ -38,7 +38,7 @@ pub struct DataclassArgsValidator { dataclass_name: String, validator_name: String, extra_behavior: ExtraBehavior, - extra_schema: Option>, + extras_schema: Option>, loc_by_alias: bool, } @@ -56,9 +56,9 @@ impl BuildValidator for DataclassArgsValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; - let extra_schema = match (schema.get_item(intern!(py, "extra_schema")), &extra_behavior) { + let extras_schema = match (schema.get_item(intern!(py, "extras_schema")), &extra_behavior) { (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, definitions)?)), - (Some(_), _) => return py_schema_err!("extra_schema can only be used if extra_behavior=allow"), + (Some(_), _) => return py_schema_err!("extras_schema can only be used if extra_behavior=allow"), (_, _) => None, }; @@ -125,7 +125,7 @@ impl BuildValidator for DataclassArgsValidator { dataclass_name, validator_name, extra_behavior, - extra_schema, + extras_schema, loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), } .into()) @@ -275,7 +275,7 @@ impl Validator for DataclassArgsValidator { } ExtraBehavior::Ignore => {} ExtraBehavior::Allow => { - if let Some(ref validator) = self.extra_schema { + if let Some(ref validator) = self.extras_schema { match validator.validate(py, value, state) { Ok(value) => output_dict .set_item(either_str.as_py_string(py), value)?, diff --git a/src/validators/model_fields.rs b/src/validators/model_fields.rs index a38fc46ec..bed636368 100644 --- a/src/validators/model_fields.rs +++ b/src/validators/model_fields.rs @@ -36,7 +36,7 @@ pub struct ModelFieldsValidator { fields: Vec, model_name: String, extra_behavior: ExtraBehavior, - extra_schema: Option>, + extras_schema: Option>, strict: bool, from_attributes: bool, loc_by_alias: bool, @@ -58,9 +58,9 @@ impl BuildValidator for ModelFieldsValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; - let extra_schema = match (schema.get_item(intern!(py, "extra_schema")), &extra_behavior) { + let extras_schema = match (schema.get_item(intern!(py, "extras_schema")), &extra_behavior) { (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, definitions)?)), - (Some(_), _) => return py_schema_err!("extra_schema can only be used if extra_behavior=allow"), + (Some(_), _) => return py_schema_err!("extras_schema can only be used if extra_behavior=allow"), (_, _) => None, }; let model_name: String = schema @@ -102,7 +102,7 @@ impl BuildValidator for ModelFieldsValidator { fields, model_name, extra_behavior, - extra_schema, + extras_schema, strict, from_attributes, loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), @@ -111,7 +111,7 @@ impl BuildValidator for ModelFieldsValidator { } } -impl_py_gc_traverse!(ModelFieldsValidator { fields, extra_schema }); +impl_py_gc_traverse!(ModelFieldsValidator { fields, extras_schema }); impl Validator for ModelFieldsValidator { fn validate<'data>( @@ -262,7 +262,7 @@ impl Validator for ModelFieldsValidator { ExtraBehavior::Ignore => {} ExtraBehavior::Allow => { let py_key = either_str.as_py_string(py); - if let Some(ref validator) = self.extra_schema { + if let Some(ref validator) = self.extras_schema { match validator.validate(py, value, state) { Ok(value) => { model_extra_dict.set_item(py_key, value)?; @@ -370,7 +370,7 @@ impl Validator for ModelFieldsValidator { // For models / typed dicts we forbid assigning extra attributes // unless the user explicitly set extra_behavior to 'allow' match self.extra_behavior { - ExtraBehavior::Allow => match self.extra_schema { + ExtraBehavior::Allow => match self.extras_schema { Some(ref validator) => prepare_result( state.with_new_extra(new_extra, |state| validator.validate(py, field_value, state)), ), @@ -427,7 +427,7 @@ impl Validator for ModelFieldsValidator { self.fields .iter_mut() .try_for_each(|f| f.validator.complete(definitions))?; - match &mut self.extra_schema { + match &mut self.extras_schema { Some(v) => v.complete(definitions), None => Ok(()), } diff --git a/src/validators/tuple.rs b/src/validators/tuple.rs index fd2b57f90..9a0517965 100644 --- a/src/validators/tuple.rs +++ b/src/validators/tuple.rs @@ -91,7 +91,7 @@ impl Validator for TupleVariableValidator { pub struct TuplePositionalValidator { strict: bool, items_validators: Vec, - extra_schema: Option>, + extras_schema: Option>, name: String, } @@ -117,7 +117,7 @@ impl BuildValidator for TuplePositionalValidator { Ok(Self { strict: is_strict(schema, config)?, items_validators: validators, - extra_schema: match schema.get_item(intern!(py, "extra_schema")) { + extras_schema: match schema.get_item(intern!(py, "extras_schema")) { Some(v) => Some(Box::new(build_validator(v, config, definitions)?)), None => None, }, @@ -134,7 +134,7 @@ fn validate_tuple_positional<'s, 'data, T: Iterator>, state: &mut ValidationState, output: &mut Vec, errors: &mut Vec>, - extra_schema: &Option>, + extras_schema: &Option>, items_validators: &[CombinedValidator], collection_iter: &mut T, collection_len: Option, @@ -160,8 +160,8 @@ fn validate_tuple_positional<'s, 'data, T: Iterator>, } for (index, result) in collection_iter.enumerate() { let item = result?; - match extra_schema { - Some(ref extra_schema) => match extra_schema.validate(py, item, state) { + match extras_schema { + Some(ref extras_schema) => match extras_schema.validate(py, item, state) { Ok(item) => output.push(item), Err(ValError::LineErrors(line_errors)) => { errors.extend( @@ -193,7 +193,7 @@ fn validate_tuple_positional<'s, 'data, T: Iterator>, impl_py_gc_traverse!(TuplePositionalValidator { items_validators, - extra_schema + extras_schema }); impl Validator for TuplePositionalValidator { @@ -218,7 +218,7 @@ impl Validator for TuplePositionalValidator { state, &mut output, &mut errors, - &self.extra_schema, + &self.extras_schema, &self.items_validators, &mut $collection_iter, collection_len, @@ -252,7 +252,7 @@ impl Validator for TuplePositionalValidator { .any(|v| v.different_strict_behavior(definitions, true)) { true - } else if let Some(ref v) = self.extra_schema { + } else if let Some(ref v) = self.extras_schema { v.different_strict_behavior(definitions, true) } else { false @@ -270,7 +270,7 @@ impl Validator for TuplePositionalValidator { self.items_validators .iter_mut() .try_for_each(|v| v.complete(definitions))?; - match &mut self.extra_schema { + match &mut self.extras_schema { Some(v) => v.complete(definitions), None => Ok(()), } diff --git a/src/validators/typed_dict.rs b/src/validators/typed_dict.rs index f2b4ccc9f..8a9ee4756 100644 --- a/src/validators/typed_dict.rs +++ b/src/validators/typed_dict.rs @@ -35,7 +35,7 @@ impl_py_gc_traverse!(TypedDictField { validator }); pub struct TypedDictValidator { fields: Vec, extra_behavior: ExtraBehavior, - extra_schema: Option>, + extras_schema: Option>, strict: bool, loc_by_alias: bool, } @@ -61,9 +61,9 @@ impl BuildValidator for TypedDictValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; - let extra_schema = match (schema.get_item(intern!(py, "extra_schema")), &extra_behavior) { + let extras_schema = match (schema.get_item(intern!(py, "extras_schema")), &extra_behavior) { (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, definitions)?)), - (Some(_), _) => return py_schema_err!("extra_schema can only be used if extra_behavior=allow"), + (Some(_), _) => return py_schema_err!("extras_schema can only be used if extra_behavior=allow"), (_, _) => None, }; @@ -129,7 +129,7 @@ impl BuildValidator for TypedDictValidator { Ok(Self { fields, extra_behavior, - extra_schema, + extras_schema, strict, loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), } @@ -137,7 +137,7 @@ impl BuildValidator for TypedDictValidator { } } -impl_py_gc_traverse!(TypedDictValidator { fields, extra_schema }); +impl_py_gc_traverse!(TypedDictValidator { fields, extras_schema }); impl Validator for TypedDictValidator { fn validate<'data>( @@ -258,7 +258,7 @@ impl Validator for TypedDictValidator { ExtraBehavior::Ignore => {} ExtraBehavior::Allow => { let py_key = either_str.as_py_string(py); - if let Some(ref validator) = self.extra_schema { + if let Some(ref validator) = self.extras_schema { match validator.validate(py, value, state) { Ok(value) => { output_dict.set_item(py_key, value)?; @@ -311,7 +311,7 @@ impl Validator for TypedDictValidator { self.fields .iter_mut() .try_for_each(|f| f.validator.complete(definitions))?; - match &mut self.extra_schema { + match &mut self.extras_schema { Some(v) => v.complete(definitions), None => Ok(()), } diff --git a/tests/benchmarks/test_micro_benchmarks.py b/tests/benchmarks/test_micro_benchmarks.py index 4ac2d30ab..6c9de2fb2 100644 --- a/tests/benchmarks/test_micro_benchmarks.py +++ b/tests/benchmarks/test_micro_benchmarks.py @@ -875,7 +875,7 @@ def test_tuple_many_variable(benchmark): @pytest.mark.benchmark(group='tuple-many') def test_tuple_many_positional(benchmark): - v = SchemaValidator({'type': 'tuple-positional', 'items_schema': [], 'extra_schema': {'type': 'int'}}) + v = SchemaValidator({'type': 'tuple-positional', 'items_schema': [], 'extras_schema': {'type': 'int'}}) assert v.validate_python(list(range(10))) == tuple(range(10)) benchmark(v.validate_python, list(range(10))) diff --git a/tests/serializers/test_dataclasses.py b/tests/serializers/test_dataclasses.py index 448e04d9b..eb4bede97 100644 --- a/tests/serializers/test_dataclasses.py +++ b/tests/serializers/test_dataclasses.py @@ -178,7 +178,7 @@ class Model: 'Model', [], extra_behavior='allow', - # extra_schema=core_schema.any_schema( + # extras_schema=core_schema.any_schema( # serialization=core_schema.plain_serializer_function_ser_schema( # lambda v: v + ' bam!', # ) diff --git a/tests/serializers/test_list_tuple.py b/tests/serializers/test_list_tuple.py index 836c7499d..9149941e1 100644 --- a/tests/serializers/test_list_tuple.py +++ b/tests/serializers/test_list_tuple.py @@ -358,7 +358,7 @@ def f(prefix, value, _info): serialization=core_schema.plain_serializer_function_ser_schema(partial(f, 'b'), info_arg=True) ), ], - 'extra_schema': core_schema.any_schema( + 'extras_schema': core_schema.any_schema( serialization=core_schema.plain_serializer_function_ser_schema(partial(f, 'extra'), info_arg=True) ), } @@ -396,7 +396,7 @@ def test_tuple_pos_dict_key(): s = SchemaSerializer( core_schema.dict_schema( core_schema.tuple_positional_schema( - [core_schema.int_schema(), core_schema.str_schema()], extra_schema=core_schema.int_schema() + [core_schema.int_schema(), core_schema.str_schema()], extras_schema=core_schema.int_schema() ), core_schema.int_schema(), ) diff --git a/tests/serializers/test_model.py b/tests/serializers/test_model.py index dbf5172a9..16e213b0d 100644 --- a/tests/serializers/test_model.py +++ b/tests/serializers/test_model.py @@ -927,7 +927,7 @@ class Model: core_schema.model_fields_schema( {}, extra_behavior='allow', - extra_schema=core_schema.any_schema( + extras_schema=core_schema.any_schema( serialization=core_schema.plain_serializer_function_ser_schema(lambda v: v + ' bam!') ), ), diff --git a/tests/serializers/test_typed_dict.py b/tests/serializers/test_typed_dict.py index 4bb66e62c..df507a248 100644 --- a/tests/serializers/test_typed_dict.py +++ b/tests/serializers/test_typed_dict.py @@ -324,7 +324,7 @@ def test_extra_custom_serializer(): schema = core_schema.typed_dict_schema( {}, extra_behavior='allow', - extra_schema=core_schema.any_schema( + extras_schema=core_schema.any_schema( serialization=core_schema.plain_serializer_function_ser_schema(lambda v: v + ' bam!') ), ) diff --git a/tests/test_json.py b/tests/test_json.py index 45d811639..272fb5dcb 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -142,7 +142,7 @@ def test_error_loc(): 'fields': { 'field_a': {'type': 'typed-dict-field', 'schema': {'type': 'list', 'items_schema': {'type': 'int'}}} }, - 'extra_schema': {'type': 'int'}, + 'extras_schema': {'type': 'int'}, 'extra_behavior': 'allow', } ) diff --git a/tests/validators/test_model_fields.py b/tests/validators/test_model_fields.py index 3f76b0da1..dbba463e2 100644 --- a/tests/validators/test_model_fields.py +++ b/tests/validators/test_model_fields.py @@ -210,9 +210,9 @@ def test_forbid_extra(): def test_allow_extra_invalid(): - with pytest.raises(SchemaError, match='extra_schema can only be used if extra_behavior=allow'): + with pytest.raises(SchemaError, match='extras_schema can only be used if extra_behavior=allow'): SchemaValidator( - {'type': 'model-fields', 'fields': {}, 'extra_schema': {'type': 'int'}, 'extra_behavior': 'ignore'} + {'type': 'model-fields', 'fields': {}, 'extras_schema': {'type': 'int'}, 'extra_behavior': 'ignore'} ) @@ -358,7 +358,7 @@ def test_validate_assignment_allow_extra_validate(): { 'type': 'model-fields', 'fields': {'field_a': {'type': 'model-field', 'schema': {'type': 'str'}}}, - 'extra_schema': {'type': 'int'}, + 'extras_schema': {'type': 'int'}, 'extra_behavior': 'allow', } ) @@ -1659,19 +1659,19 @@ def test_frozen_field(): ], ) @pytest.mark.parametrize( - 'extra_schema_kw, expected_extra_value', - [({}, '123'), ({'extra_schema': None}, '123'), ({'extra_schema': core_schema.int_schema()}, 123)], - ids=['extra_schema=unset', 'extra_schema=None', 'extra_schema=int'], + 'extras_schema_kw, expected_extra_value', + [({}, '123'), ({'extras_schema': None}, '123'), ({'extras_schema': core_schema.int_schema()}, 123)], + ids=['extras_schema=unset', 'extras_schema=None', 'extras_schema=int'], ) def test_extra_behavior_allow( config: Union[core_schema.CoreConfig, None], schema_extra_behavior_kw: Dict[str, Any], - extra_schema_kw: Dict[str, Any], + extras_schema_kw: Dict[str, Any], expected_extra_value: Any, ): v = SchemaValidator( core_schema.model_fields_schema( - {'f': core_schema.model_field(core_schema.str_schema())}, **schema_extra_behavior_kw, **extra_schema_kw + {'f': core_schema.model_field(core_schema.str_schema())}, **schema_extra_behavior_kw, **extras_schema_kw ), config=config, ) diff --git a/tests/validators/test_tuple.py b/tests/validators/test_tuple.py index 9e365050f..b7f3e9720 100644 --- a/tests/validators/test_tuple.py +++ b/tests/validators/test_tuple.py @@ -264,7 +264,7 @@ def test_positional_empty(py_and_json: PyAndJson): def test_positional_empty_extra(py_and_json: PyAndJson): - v = py_and_json({'type': 'tuple-positional', 'items_schema': [], 'extra_schema': {'type': 'int'}}) + v = py_and_json({'type': 'tuple-positional', 'items_schema': [], 'extras_schema': {'type': 'int'}}) assert v.validate_test([]) == () assert v.validate_python(()) == () assert v.validate_test([1]) == (1,) @@ -408,7 +408,7 @@ def test_tuple_fix_extra(input_value, expected, cache): { 'type': 'tuple-positional', 'items_schema': [{'type': 'int'}, {'type': 'str'}], - 'extra_schema': {'type': 'str'}, + 'extras_schema': {'type': 'str'}, } ) @@ -422,7 +422,7 @@ def test_tuple_fix_extra(input_value, expected, cache): def test_tuple_fix_extra_any(): v = SchemaValidator( - {'type': 'tuple-positional', 'items_schema': [{'type': 'str'}], 'extra_schema': {'type': 'any'}} + {'type': 'tuple-positional', 'items_schema': [{'type': 'str'}], 'extras_schema': {'type': 'any'}} ) assert v.validate_python([b'1']) == ('1',) assert v.validate_python([b'1', 2]) == ('1', 2) diff --git a/tests/validators/test_typed_dict.py b/tests/validators/test_typed_dict.py index bced2beb5..1d3b694f1 100644 --- a/tests/validators/test_typed_dict.py +++ b/tests/validators/test_typed_dict.py @@ -186,9 +186,9 @@ def test_forbid_extra(): def test_allow_extra_invalid(): - with pytest.raises(SchemaError, match='extra_schema can only be used if extra_behavior=allow'): + with pytest.raises(SchemaError, match='extras_schema can only be used if extra_behavior=allow'): SchemaValidator( - {'type': 'typed-dict', 'fields': {}, 'extra_schema': {'type': 'int'}, 'extra_behavior': 'ignore'} + {'type': 'typed-dict', 'fields': {}, 'extras_schema': {'type': 'int'}, 'extra_behavior': 'ignore'} ) @@ -1089,21 +1089,21 @@ def wrap_function(input_value, validator, info): ], ) @pytest.mark.parametrize( - 'extra_schema_kw, expected_extra_value', - [({}, '123'), ({'extra_schema': None}, '123'), ({'extra_schema': core_schema.int_schema()}, 123)], - ids=['extra_schema=unset', 'extra_schema=None', 'extra_schema=int'], + 'extras_schema_kw, expected_extra_value', + [({}, '123'), ({'extras_schema': None}, '123'), ({'extras_schema': core_schema.int_schema()}, 123)], + ids=['extras_schema=unset', 'extras_schema=None', 'extras_schema=int'], ) def test_extra_behavior_allow( config: Union[core_schema.CoreConfig, None], schema_extra_behavior_kw: Dict[str, Any], - extra_schema_kw: Dict[str, Any], + extras_schema_kw: Dict[str, Any], expected_extra_value: Any, ): v = SchemaValidator( core_schema.typed_dict_schema( {'f': core_schema.typed_dict_field(core_schema.str_schema())}, **schema_extra_behavior_kw, - **extra_schema_kw, + **extras_schema_kw, config=config, ) ) @@ -1173,7 +1173,7 @@ def validate(v, info): schema = core_schema.general_plain_validator_function(validate) schema = core_schema.typed_dict_schema( - {'f': core_schema.typed_dict_field(schema)}, extra_behavior='allow', extra_schema=schema + {'f': core_schema.typed_dict_field(schema)}, extra_behavior='allow', extras_schema=schema ) # If any of the Rust validators don't implement traversal properly, diff --git a/tests/validators/test_with_default.py b/tests/validators/test_with_default.py index 266bd0cda..e0ebd2fb3 100644 --- a/tests/validators/test_with_default.py +++ b/tests/validators/test_with_default.py @@ -189,7 +189,7 @@ def test_tuple_positional_omit(): { 'type': 'tuple-positional', 'items_schema': [{'type': 'int'}, {'type': 'int'}], - 'extra_schema': {'type': 'default', 'schema': {'type': 'int'}, 'on_error': 'omit'}, + 'extras_schema': {'type': 'default', 'schema': {'type': 'int'}, 'on_error': 'omit'}, } ) assert v.validate_python((1, '2')) == (1, 2) From 7f1aaa68feb1a1dca02e5b8323120c42390694e3 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 23 Aug 2023 10:37:10 -0500 Subject: [PATCH 4/4] renaming --- src/validators/dataclass.rs | 8 ++++---- src/validators/model_fields.rs | 17 ++++++++++------- src/validators/tuple.rs | 18 +++++++++--------- src/validators/typed_dict.rs | 15 +++++++++------ 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/validators/dataclass.rs b/src/validators/dataclass.rs index c6999d4ec..7b7be282e 100644 --- a/src/validators/dataclass.rs +++ b/src/validators/dataclass.rs @@ -38,7 +38,7 @@ pub struct DataclassArgsValidator { dataclass_name: String, validator_name: String, extra_behavior: ExtraBehavior, - extras_schema: Option>, + extras_validator: Option>, loc_by_alias: bool, } @@ -56,7 +56,7 @@ impl BuildValidator for DataclassArgsValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; - let extras_schema = match (schema.get_item(intern!(py, "extras_schema")), &extra_behavior) { + let extras_validator = match (schema.get_item(intern!(py, "extras_schema")), &extra_behavior) { (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, definitions)?)), (Some(_), _) => return py_schema_err!("extras_schema can only be used if extra_behavior=allow"), (_, _) => None, @@ -125,7 +125,7 @@ impl BuildValidator for DataclassArgsValidator { dataclass_name, validator_name, extra_behavior, - extras_schema, + extras_validator, loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), } .into()) @@ -275,7 +275,7 @@ impl Validator for DataclassArgsValidator { } ExtraBehavior::Ignore => {} ExtraBehavior::Allow => { - if let Some(ref validator) = self.extras_schema { + if let Some(ref validator) = self.extras_validator { match validator.validate(py, value, state) { Ok(value) => output_dict .set_item(either_str.as_py_string(py), value)?, diff --git a/src/validators/model_fields.rs b/src/validators/model_fields.rs index bed636368..29e9522f2 100644 --- a/src/validators/model_fields.rs +++ b/src/validators/model_fields.rs @@ -36,7 +36,7 @@ pub struct ModelFieldsValidator { fields: Vec, model_name: String, extra_behavior: ExtraBehavior, - extras_schema: Option>, + extras_validator: Option>, strict: bool, from_attributes: bool, loc_by_alias: bool, @@ -58,7 +58,7 @@ impl BuildValidator for ModelFieldsValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; - let extras_schema = match (schema.get_item(intern!(py, "extras_schema")), &extra_behavior) { + let extras_validator = match (schema.get_item(intern!(py, "extras_schema")), &extra_behavior) { (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, definitions)?)), (Some(_), _) => return py_schema_err!("extras_schema can only be used if extra_behavior=allow"), (_, _) => None, @@ -102,7 +102,7 @@ impl BuildValidator for ModelFieldsValidator { fields, model_name, extra_behavior, - extras_schema, + extras_validator, strict, from_attributes, loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), @@ -111,7 +111,10 @@ impl BuildValidator for ModelFieldsValidator { } } -impl_py_gc_traverse!(ModelFieldsValidator { fields, extras_schema }); +impl_py_gc_traverse!(ModelFieldsValidator { + fields, + extras_validator +}); impl Validator for ModelFieldsValidator { fn validate<'data>( @@ -262,7 +265,7 @@ impl Validator for ModelFieldsValidator { ExtraBehavior::Ignore => {} ExtraBehavior::Allow => { let py_key = either_str.as_py_string(py); - if let Some(ref validator) = self.extras_schema { + if let Some(ref validator) = self.extras_validator { match validator.validate(py, value, state) { Ok(value) => { model_extra_dict.set_item(py_key, value)?; @@ -370,7 +373,7 @@ impl Validator for ModelFieldsValidator { // For models / typed dicts we forbid assigning extra attributes // unless the user explicitly set extra_behavior to 'allow' match self.extra_behavior { - ExtraBehavior::Allow => match self.extras_schema { + ExtraBehavior::Allow => match self.extras_validator { Some(ref validator) => prepare_result( state.with_new_extra(new_extra, |state| validator.validate(py, field_value, state)), ), @@ -427,7 +430,7 @@ impl Validator for ModelFieldsValidator { self.fields .iter_mut() .try_for_each(|f| f.validator.complete(definitions))?; - match &mut self.extras_schema { + match &mut self.extras_validator { Some(v) => v.complete(definitions), None => Ok(()), } diff --git a/src/validators/tuple.rs b/src/validators/tuple.rs index 9a0517965..07887fddb 100644 --- a/src/validators/tuple.rs +++ b/src/validators/tuple.rs @@ -91,7 +91,7 @@ impl Validator for TupleVariableValidator { pub struct TuplePositionalValidator { strict: bool, items_validators: Vec, - extras_schema: Option>, + extras_validator: Option>, name: String, } @@ -117,7 +117,7 @@ impl BuildValidator for TuplePositionalValidator { Ok(Self { strict: is_strict(schema, config)?, items_validators: validators, - extras_schema: match schema.get_item(intern!(py, "extras_schema")) { + extras_validator: match schema.get_item(intern!(py, "extras_schema")) { Some(v) => Some(Box::new(build_validator(v, config, definitions)?)), None => None, }, @@ -134,7 +134,7 @@ fn validate_tuple_positional<'s, 'data, T: Iterator>, state: &mut ValidationState, output: &mut Vec, errors: &mut Vec>, - extras_schema: &Option>, + extras_validator: &Option>, items_validators: &[CombinedValidator], collection_iter: &mut T, collection_len: Option, @@ -160,8 +160,8 @@ fn validate_tuple_positional<'s, 'data, T: Iterator>, } for (index, result) in collection_iter.enumerate() { let item = result?; - match extras_schema { - Some(ref extras_schema) => match extras_schema.validate(py, item, state) { + match extras_validator { + Some(ref extras_validator) => match extras_validator.validate(py, item, state) { Ok(item) => output.push(item), Err(ValError::LineErrors(line_errors)) => { errors.extend( @@ -193,7 +193,7 @@ fn validate_tuple_positional<'s, 'data, T: Iterator>, impl_py_gc_traverse!(TuplePositionalValidator { items_validators, - extras_schema + extras_validator }); impl Validator for TuplePositionalValidator { @@ -218,7 +218,7 @@ impl Validator for TuplePositionalValidator { state, &mut output, &mut errors, - &self.extras_schema, + &self.extras_validator, &self.items_validators, &mut $collection_iter, collection_len, @@ -252,7 +252,7 @@ impl Validator for TuplePositionalValidator { .any(|v| v.different_strict_behavior(definitions, true)) { true - } else if let Some(ref v) = self.extras_schema { + } else if let Some(ref v) = self.extras_validator { v.different_strict_behavior(definitions, true) } else { false @@ -270,7 +270,7 @@ impl Validator for TuplePositionalValidator { self.items_validators .iter_mut() .try_for_each(|v| v.complete(definitions))?; - match &mut self.extras_schema { + match &mut self.extras_validator { Some(v) => v.complete(definitions), None => Ok(()), } diff --git a/src/validators/typed_dict.rs b/src/validators/typed_dict.rs index 8a9ee4756..a095a52f1 100644 --- a/src/validators/typed_dict.rs +++ b/src/validators/typed_dict.rs @@ -35,7 +35,7 @@ impl_py_gc_traverse!(TypedDictField { validator }); pub struct TypedDictValidator { fields: Vec, extra_behavior: ExtraBehavior, - extras_schema: Option>, + extras_validator: Option>, strict: bool, loc_by_alias: bool, } @@ -61,7 +61,7 @@ impl BuildValidator for TypedDictValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; - let extras_schema = match (schema.get_item(intern!(py, "extras_schema")), &extra_behavior) { + let extras_validator = match (schema.get_item(intern!(py, "extras_schema")), &extra_behavior) { (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(v, config, definitions)?)), (Some(_), _) => return py_schema_err!("extras_schema can only be used if extra_behavior=allow"), (_, _) => None, @@ -129,7 +129,7 @@ impl BuildValidator for TypedDictValidator { Ok(Self { fields, extra_behavior, - extras_schema, + extras_validator, strict, loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), } @@ -137,7 +137,10 @@ impl BuildValidator for TypedDictValidator { } } -impl_py_gc_traverse!(TypedDictValidator { fields, extras_schema }); +impl_py_gc_traverse!(TypedDictValidator { + fields, + extras_validator +}); impl Validator for TypedDictValidator { fn validate<'data>( @@ -258,7 +261,7 @@ impl Validator for TypedDictValidator { ExtraBehavior::Ignore => {} ExtraBehavior::Allow => { let py_key = either_str.as_py_string(py); - if let Some(ref validator) = self.extras_schema { + if let Some(ref validator) = self.extras_validator { match validator.validate(py, value, state) { Ok(value) => { output_dict.set_item(py_key, value)?; @@ -311,7 +314,7 @@ impl Validator for TypedDictValidator { self.fields .iter_mut() .try_for_each(|f| f.validator.complete(definitions))?; - match &mut self.extras_schema { + match &mut self.extras_validator { Some(v) => v.complete(definitions), None => Ok(()), }