Skip to content

Commit 74c3cd9

Browse files
committed
Add extra parameter to the validate functions
1 parent 5c56fde commit 74c3cd9

20 files changed

+486
-180
lines changed

benches/main.rs

Lines changed: 55 additions & 55 deletions
Large diffs are not rendered by default.

python/pydantic_core/_pydantic_core.pyi

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ from _typeshed import SupportsAllComparisons
66
from typing_extensions import LiteralString, Self, TypeAlias
77

88
from pydantic_core import ErrorDetails, ErrorTypeInfo, InitErrorDetails, MultiHostHost
9-
from pydantic_core.core_schema import CoreConfig, CoreSchema, ErrorType
9+
from pydantic_core.core_schema import CoreConfig, CoreSchema, ErrorType, ExtraBehavior
1010

1111
__all__ = [
1212
'__version__',
@@ -93,6 +93,7 @@ class SchemaValidator:
9393
input: Any,
9494
*,
9595
strict: bool | None = None,
96+
extra: ExtraBehavior | None = None,
9697
from_attributes: bool | None = None,
9798
context: Any | None = None,
9899
self_instance: Any | None = None,
@@ -107,6 +108,8 @@ class SchemaValidator:
107108
input: The Python object to validate.
108109
strict: Whether to validate the object in strict mode.
109110
If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used.
111+
extra: The behavior for handling extra fields.
112+
If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used.
110113
from_attributes: Whether to validate objects as inputs to models by extracting attributes.
111114
If `None`, the value of [`CoreConfig.from_attributes`][pydantic_core.core_schema.CoreConfig] is used.
112115
context: The context to use for validation, this is passed to functional validators as
@@ -131,6 +134,7 @@ class SchemaValidator:
131134
input: Any,
132135
*,
133136
strict: bool | None = None,
137+
extra: ExtraBehavior | None = None,
134138
from_attributes: bool | None = None,
135139
context: Any | None = None,
136140
self_instance: Any | None = None,
@@ -151,6 +155,7 @@ class SchemaValidator:
151155
input: str | bytes | bytearray,
152156
*,
153157
strict: bool | None = None,
158+
extra: ExtraBehavior | None = None,
154159
context: Any | None = None,
155160
self_instance: Any | None = None,
156161
allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False,
@@ -170,6 +175,8 @@ class SchemaValidator:
170175
input: The JSON data to validate.
171176
strict: Whether to validate the object in strict mode.
172177
If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used.
178+
extra: The behavior for handling extra fields.
179+
If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used.
173180
context: The context to use for validation, this is passed to functional validators as
174181
[`info.context`][pydantic_core.core_schema.ValidationInfo.context].
175182
self_instance: An instance of a model set attributes on from validation.
@@ -191,6 +198,7 @@ class SchemaValidator:
191198
input: _StringInput,
192199
*,
193200
strict: bool | None = None,
201+
extra: ExtraBehavior | None = None,
194202
context: Any | None = None,
195203
allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False,
196204
by_alias: bool | None = None,
@@ -206,6 +214,8 @@ class SchemaValidator:
206214
input: The input as a string, or bytes/bytearray if `strict=False`.
207215
strict: Whether to validate the object in strict mode.
208216
If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used.
217+
extra: The behavior for handling extra fields.
218+
If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used.
209219
context: The context to use for validation, this is passed to functional validators as
210220
[`info.context`][pydantic_core.core_schema.ValidationInfo.context].
211221
allow_partial: Whether to allow partial validation; if `True` errors in the last element of sequences
@@ -228,6 +238,7 @@ class SchemaValidator:
228238
field_value: Any,
229239
*,
230240
strict: bool | None = None,
241+
extra: ExtraBehavior | None = None,
231242
from_attributes: bool | None = None,
232243
context: Any | None = None,
233244
by_alias: bool | None = None,
@@ -242,6 +253,8 @@ class SchemaValidator:
242253
field_value: The value to assign to the field.
243254
strict: Whether to validate the object in strict mode.
244255
If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used.
256+
extra: The behavior for handling extra fields.
257+
If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used.
245258
from_attributes: Whether to validate objects as inputs to models by extracting attributes.
246259
If `None`, the value of [`CoreConfig.from_attributes`][pydantic_core.core_schema.CoreConfig] is used.
247260
context: The context to use for validation, this is passed to functional validators as

src/build_tools.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::error::Error;
22
use std::fmt;
3+
use std::str::FromStr;
34

45
use pyo3::exceptions::PyException;
56
use pyo3::prelude::*;
@@ -176,7 +177,7 @@ macro_rules! py_schema_err {
176177
pub(crate) use py_schema_err;
177178

178179
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
179-
pub(crate) enum ExtraBehavior {
180+
pub enum ExtraBehavior {
180181
Allow,
181182
Forbid,
182183
Ignore,
@@ -197,12 +198,22 @@ impl ExtraBehavior {
197198
)?
198199
.flatten();
199200
let res = match extra_behavior.as_ref().map(|s| s.to_str()).transpose()? {
200-
Some("allow") => Self::Allow,
201-
Some("ignore") => Self::Ignore,
202-
Some("forbid") => Self::Forbid,
203-
Some(v) => return py_schema_err!("Invalid extra_behavior: `{}`", v),
201+
Some(s) => Self::from_str(s)?,
204202
None => default,
205203
};
206204
Ok(res)
207205
}
208206
}
207+
208+
impl FromStr for ExtraBehavior {
209+
type Err = PyErr;
210+
211+
fn from_str(s: &str) -> Result<Self, Self::Err> {
212+
match s {
213+
"allow" => Ok(Self::Allow),
214+
"forbid" => Ok(Self::Forbid),
215+
"ignore" => Ok(Self::Ignore),
216+
s => py_schema_err!("Invalid extra_behavior: `{}`", s),
217+
}
218+
}
219+
}

src/url.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ impl PyUrl {
4545
pub fn py_new(py: Python, url: &Bound<'_, PyAny>) -> PyResult<Self> {
4646
let schema_obj = SCHEMA_DEFINITION_URL
4747
.get_or_init(py, || build_schema_validator(py, "url"))
48-
.validate_python(py, url, None, None, None, None, false.into(), None, None)?;
48+
.validate_python(py, url, None, None, None, None, None, false.into(), None, None)?;
4949
schema_obj.extract(py)
5050
}
5151

@@ -225,7 +225,7 @@ impl PyMultiHostUrl {
225225
pub fn py_new(py: Python, url: &Bound<'_, PyAny>) -> PyResult<Self> {
226226
let schema_obj = SCHEMA_DEFINITION_MULTI_HOST_URL
227227
.get_or_init(py, || build_schema_validator(py, "multi-host-url"))
228-
.validate_python(py, url, None, None, None, None, false.into(), None, None)?;
228+
.validate_python(py, url, None, None, None, None, None, false.into(), None, None)?;
229229
schema_obj.extract(py)
230230
}
231231

src/validators/arguments_v3.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -241,13 +241,15 @@ impl ArgumentsV3Validator {
241241

242242
let validate_by_alias = state.validate_by_alias_or(self.validate_by_alias);
243243
let validate_by_name = state.validate_by_name_or(self.validate_by_name);
244+
let extra_behavior = state.extra_behavior_or(self.extra);
244245

245246
// Keep track of used keys for extra behavior:
246-
let mut used_keys: Option<AHashSet<&str>> = if self.extra == ExtraBehavior::Ignore || mapping.is_py_get_attr() {
247-
None
248-
} else {
249-
Some(AHashSet::with_capacity(self.parameters.len()))
250-
};
247+
let mut used_keys: Option<AHashSet<&str>> =
248+
if extra_behavior == ExtraBehavior::Ignore || mapping.is_py_get_attr() {
249+
None
250+
} else {
251+
Some(AHashSet::with_capacity(self.parameters.len()))
252+
};
251253

252254
for parameter in &self.parameters {
253255
let lookup_key = parameter
@@ -492,7 +494,7 @@ impl ArgumentsV3Validator {
492494
mapping.iterate(ValidateExtra {
493495
used_keys,
494496
errors: &mut errors,
495-
extra_behavior: self.extra,
497+
extra_behavior,
496498
})??;
497499
}
498500

@@ -524,6 +526,7 @@ impl ArgumentsV3Validator {
524526

525527
let validate_by_alias = state.validate_by_alias_or(self.validate_by_alias);
526528
let validate_by_name = state.validate_by_name_or(self.validate_by_name);
529+
let extra_behavior = state.extra_behavior_or(self.extra);
527530

528531
// go through non variadic parameters, getting the value from args or kwargs and validating it
529532
for (index, parameter) in self.parameters.iter().filter(|p| !p.is_variadic()).enumerate() {
@@ -687,7 +690,7 @@ impl ArgumentsV3Validator {
687690

688691
match maybe_var_kwargs_parameter {
689692
None => {
690-
if self.extra == ExtraBehavior::Forbid {
693+
if extra_behavior == ExtraBehavior::Forbid {
691694
errors.push(ValLineError::new_with_loc(
692695
ErrorTypeDefaults::UnexpectedKeywordArgument,
693696
value,

src/validators/dataclass.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ impl Validator for DataclassArgsValidator {
155155
let mut used_keys: AHashSet<&str> = AHashSet::with_capacity(self.fields.len());
156156

157157
let state = &mut state.rebind_extra(|extra| extra.data = Some(output_dict.clone()));
158+
let extra_behavior = state.extra_behavior_or(self.extra_behavior);
158159

159160
let validate_by_alias = state.validate_by_alias_or(self.validate_by_alias);
160161
let validate_by_name = state.validate_by_name_or(self.validate_by_name);
@@ -308,7 +309,7 @@ impl Validator for DataclassArgsValidator {
308309
Ok(either_str) => {
309310
if !used_keys.contains(either_str.as_cow()?.as_ref()) {
310311
// Unknown / extra field
311-
match self.extra_behavior {
312+
match extra_behavior {
312313
ExtraBehavior::Forbid => {
313314
errors.push(ValLineError::new_with_loc(
314315
ErrorTypeDefaults::UnexpectedKeywordArgument,
@@ -379,6 +380,7 @@ impl Validator for DataclassArgsValidator {
379380
state: &mut ValidationState<'_, 'py>,
380381
) -> ValResult<PyObject> {
381382
let dict = obj.downcast::<PyDict>()?;
383+
let extra_behavior = state.extra_behavior_or(self.extra_behavior);
382384

383385
let ok = |output: PyObject| {
384386
dict.set_item(field_name, output)?;
@@ -426,7 +428,7 @@ impl Validator for DataclassArgsValidator {
426428
// Handle extra (unknown) field
427429
// We partially use the extra_behavior for initialization / validation
428430
// to determine how to handle assignment
429-
match self.extra_behavior {
431+
match extra_behavior {
430432
// For dataclasses we allow assigning unknown fields
431433
// to match stdlib dataclass behavior
432434
ExtraBehavior::Allow => ok(field_value.clone().unbind()),

src/validators/generator.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::sync::Arc;
44
use pyo3::types::{PyDict, PyString};
55
use pyo3::{prelude::*, IntoPyObjectExt, PyTraverseError, PyVisit};
66

7+
use crate::build_tools::ExtraBehavior;
78
use crate::errors::{ErrorType, LocItem, ValError, ValResult};
89
use crate::input::{BorrowInput, GenericIterator, Input};
910
use crate::py_gc::PyGcTraverse;
@@ -220,6 +221,7 @@ pub struct InternalValidator {
220221
// TODO, do we need data?
221222
data: Option<Py<PyDict>>,
222223
strict: Option<bool>,
224+
extra_behavior: Option<ExtraBehavior>,
223225
from_attributes: Option<bool>,
224226
context: Option<PyObject>,
225227
self_instance: Option<PyObject>,
@@ -252,6 +254,7 @@ impl InternalValidator {
252254
validator,
253255
data: extra.data.as_ref().map(|d| d.clone().into()),
254256
strict: extra.strict,
257+
extra_behavior: extra.extra_behavior,
255258
from_attributes: extra.from_attributes,
256259
context: extra.context.map(|d| d.clone().unbind()),
257260
self_instance: extra.self_instance.map(|d| d.clone().unbind()),
@@ -277,6 +280,7 @@ impl InternalValidator {
277280
input_type: self.validation_mode,
278281
data: self.data.as_ref().map(|data| data.bind(py).clone()),
279282
strict: self.strict,
283+
extra_behavior: self.extra_behavior,
280284
from_attributes: self.from_attributes,
281285
field_name: Some(PyString::new(py, field_name)),
282286
context: self.context.as_ref().map(|data| data.bind(py)),
@@ -315,6 +319,7 @@ impl InternalValidator {
315319
input_type: self.validation_mode,
316320
data: self.data.as_ref().map(|data| data.bind(py).clone()),
317321
strict: self.strict,
322+
extra_behavior: self.extra_behavior,
318323
from_attributes: self.from_attributes,
319324
field_name: None,
320325
context: self.context.as_ref().map(|data| data.bind(py)),

0 commit comments

Comments
 (0)