Skip to content

Add extra parameter to the validate functions #1722

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 58 additions & 58 deletions benches/main.rs

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion python/pydantic_core/_pydantic_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ from _typeshed import SupportsAllComparisons
from typing_extensions import LiteralString, Self, TypeAlias

from pydantic_core import ErrorDetails, ErrorTypeInfo, InitErrorDetails, MultiHostHost
from pydantic_core.core_schema import CoreConfig, CoreSchema, ErrorType
from pydantic_core.core_schema import CoreConfig, CoreSchema, ErrorType, ExtraBehavior

__all__ = [
'__version__',
Expand Down Expand Up @@ -93,6 +93,7 @@ class SchemaValidator:
input: Any,
*,
strict: bool | None = None,
extra: ExtraBehavior | None = None,
from_attributes: bool | None = None,
context: Any | None = None,
self_instance: Any | None = None,
Expand All @@ -107,6 +108,8 @@ class SchemaValidator:
input: The Python object to validate.
strict: Whether to validate the object in strict mode.
If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used.
extra: The behavior for handling extra fields.
If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used.
from_attributes: Whether to validate objects as inputs to models by extracting attributes.
If `None`, the value of [`CoreConfig.from_attributes`][pydantic_core.core_schema.CoreConfig] is used.
context: The context to use for validation, this is passed to functional validators as
Expand All @@ -131,6 +134,7 @@ class SchemaValidator:
input: Any,
*,
strict: bool | None = None,
extra: ExtraBehavior | None = None,
from_attributes: bool | None = None,
context: Any | None = None,
self_instance: Any | None = None,
Expand All @@ -151,6 +155,7 @@ class SchemaValidator:
input: str | bytes | bytearray,
*,
strict: bool | None = None,
extra: ExtraBehavior | None = None,
context: Any | None = None,
self_instance: Any | None = None,
allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False,
Expand All @@ -170,6 +175,8 @@ class SchemaValidator:
input: The JSON data to validate.
strict: Whether to validate the object in strict mode.
If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used.
extra: The behavior for handling extra fields.
If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used.
context: The context to use for validation, this is passed to functional validators as
[`info.context`][pydantic_core.core_schema.ValidationInfo.context].
self_instance: An instance of a model set attributes on from validation.
Expand All @@ -191,6 +198,7 @@ class SchemaValidator:
input: _StringInput,
*,
strict: bool | None = None,
extra: ExtraBehavior | None = None,
context: Any | None = None,
allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False,
by_alias: bool | None = None,
Expand All @@ -206,6 +214,8 @@ class SchemaValidator:
input: The input as a string, or bytes/bytearray if `strict=False`.
strict: Whether to validate the object in strict mode.
If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used.
extra: The behavior for handling extra fields.
If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used.
context: The context to use for validation, this is passed to functional validators as
[`info.context`][pydantic_core.core_schema.ValidationInfo.context].
allow_partial: Whether to allow partial validation; if `True` errors in the last element of sequences
Expand All @@ -228,6 +238,7 @@ class SchemaValidator:
field_value: Any,
*,
strict: bool | None = None,
extra: ExtraBehavior | None = None,
from_attributes: bool | None = None,
context: Any | None = None,
by_alias: bool | None = None,
Expand All @@ -242,6 +253,8 @@ class SchemaValidator:
field_value: The value to assign to the field.
strict: Whether to validate the object in strict mode.
If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used.
extra: The behavior for handling extra fields.
If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used.
from_attributes: Whether to validate objects as inputs to models by extracting attributes.
If `None`, the value of [`CoreConfig.from_attributes`][pydantic_core.core_schema.CoreConfig] is used.
context: The context to use for validation, this is passed to functional validators as
Expand Down
21 changes: 16 additions & 5 deletions src/build_tools.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::error::Error;
use std::fmt;
use std::str::FromStr;

use pyo3::exceptions::PyException;
use pyo3::prelude::*;
Expand Down Expand Up @@ -176,7 +177,7 @@ macro_rules! py_schema_err {
pub(crate) use py_schema_err;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) enum ExtraBehavior {
pub enum ExtraBehavior {
Allow,
Forbid,
Ignore,
Expand All @@ -197,12 +198,22 @@ impl ExtraBehavior {
)?
.flatten();
let res = match extra_behavior.as_ref().map(|s| s.to_str()).transpose()? {
Some("allow") => Self::Allow,
Some("ignore") => Self::Ignore,
Some("forbid") => Self::Forbid,
Some(v) => return py_schema_err!("Invalid extra_behavior: `{}`", v),
Some(s) => Self::from_str(s)?,
None => default,
};
Ok(res)
}
}

impl FromStr for ExtraBehavior {
type Err = PyErr;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"allow" => Ok(Self::Allow),
"forbid" => Ok(Self::Forbid),
"ignore" => Ok(Self::Ignore),
s => py_schema_err!("Invalid extra_behavior: `{}`", s),
}
}
}
4 changes: 2 additions & 2 deletions src/url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl PyUrl {
pub fn py_new(py: Python, url: &Bound<'_, PyAny>) -> PyResult<Self> {
let schema_obj = SCHEMA_DEFINITION_URL
.get_or_init(py, || build_schema_validator(py, "url"))
.validate_python(py, url, None, None, None, None, false.into(), None, None)?;
.validate_python(py, url, None, None, None, None, None, false.into(), None, None)?;
schema_obj.extract(py)
}

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

Expand Down
17 changes: 10 additions & 7 deletions src/validators/arguments_v3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,15 @@ impl ArgumentsV3Validator {

let validate_by_alias = state.validate_by_alias_or(self.validate_by_alias);
let validate_by_name = state.validate_by_name_or(self.validate_by_name);
let extra_behavior = state.extra_behavior_or(self.extra);

// Keep track of used keys for extra behavior:
let mut used_keys: Option<AHashSet<&str>> = if self.extra == ExtraBehavior::Ignore || mapping.is_py_get_attr() {
None
} else {
Some(AHashSet::with_capacity(self.parameters.len()))
};
let mut used_keys: Option<AHashSet<&str>> =
if extra_behavior == ExtraBehavior::Ignore || mapping.is_py_get_attr() {
None
} else {
Some(AHashSet::with_capacity(self.parameters.len()))
};

for parameter in &self.parameters {
let lookup_key = parameter
Expand Down Expand Up @@ -492,7 +494,7 @@ impl ArgumentsV3Validator {
mapping.iterate(ValidateExtra {
used_keys,
errors: &mut errors,
extra_behavior: self.extra,
extra_behavior,
})??;
}

Expand Down Expand Up @@ -524,6 +526,7 @@ impl ArgumentsV3Validator {

let validate_by_alias = state.validate_by_alias_or(self.validate_by_alias);
let validate_by_name = state.validate_by_name_or(self.validate_by_name);
let extra_behavior = state.extra_behavior_or(self.extra);

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

match maybe_var_kwargs_parameter {
None => {
if self.extra == ExtraBehavior::Forbid {
if extra_behavior == ExtraBehavior::Forbid {
errors.push(ValLineError::new_with_loc(
ErrorTypeDefaults::UnexpectedKeywordArgument,
value,
Expand Down
6 changes: 4 additions & 2 deletions src/validators/dataclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ impl Validator for DataclassArgsValidator {
let mut used_keys: AHashSet<&str> = AHashSet::with_capacity(self.fields.len());

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

let validate_by_alias = state.validate_by_alias_or(self.validate_by_alias);
let validate_by_name = state.validate_by_name_or(self.validate_by_name);
Expand Down Expand Up @@ -308,7 +309,7 @@ impl Validator for DataclassArgsValidator {
Ok(either_str) => {
if !used_keys.contains(either_str.as_cow()?.as_ref()) {
// Unknown / extra field
match self.extra_behavior {
match extra_behavior {
ExtraBehavior::Forbid => {
errors.push(ValLineError::new_with_loc(
ErrorTypeDefaults::UnexpectedKeywordArgument,
Expand Down Expand Up @@ -379,6 +380,7 @@ impl Validator for DataclassArgsValidator {
state: &mut ValidationState<'_, 'py>,
) -> ValResult<PyObject> {
let dict = obj.downcast::<PyDict>()?;
let extra_behavior = state.extra_behavior_or(self.extra_behavior);

let ok = |output: PyObject| {
dict.set_item(field_name, output)?;
Expand Down Expand Up @@ -426,7 +428,7 @@ impl Validator for DataclassArgsValidator {
// Handle extra (unknown) field
// We partially use the extra_behavior for initialization / validation
// to determine how to handle assignment
match self.extra_behavior {
match extra_behavior {
// For dataclasses we allow assigning unknown fields
// to match stdlib dataclass behavior
ExtraBehavior::Allow => ok(field_value.clone().unbind()),
Expand Down
5 changes: 5 additions & 0 deletions src/validators/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::Arc;
use pyo3::types::{PyDict, PyString};
use pyo3::{prelude::*, IntoPyObjectExt, PyTraverseError, PyVisit};

use crate::build_tools::ExtraBehavior;
use crate::errors::{ErrorType, LocItem, ValError, ValResult};
use crate::input::{BorrowInput, GenericIterator, Input};
use crate::py_gc::PyGcTraverse;
Expand Down Expand Up @@ -220,6 +221,7 @@ pub struct InternalValidator {
// TODO, do we need data?
data: Option<Py<PyDict>>,
strict: Option<bool>,
extra_behavior: Option<ExtraBehavior>,
from_attributes: Option<bool>,
context: Option<PyObject>,
self_instance: Option<PyObject>,
Expand Down Expand Up @@ -252,6 +254,7 @@ impl InternalValidator {
validator,
data: extra.data.as_ref().map(|d| d.clone().into()),
strict: extra.strict,
extra_behavior: extra.extra_behavior,
from_attributes: extra.from_attributes,
context: extra.context.map(|d| d.clone().unbind()),
self_instance: extra.self_instance.map(|d| d.clone().unbind()),
Expand All @@ -277,6 +280,7 @@ impl InternalValidator {
input_type: self.validation_mode,
data: self.data.as_ref().map(|data| data.bind(py).clone()),
strict: self.strict,
extra_behavior: self.extra_behavior,
from_attributes: self.from_attributes,
field_name: Some(PyString::new(py, field_name)),
context: self.context.as_ref().map(|data| data.bind(py)),
Expand Down Expand Up @@ -315,6 +319,7 @@ impl InternalValidator {
input_type: self.validation_mode,
data: self.data.as_ref().map(|data| data.bind(py).clone()),
strict: self.strict,
extra_behavior: self.extra_behavior,
from_attributes: self.from_attributes,
field_name: None,
context: self.context.as_ref().map(|data| data.bind(py)),
Expand Down
Loading
Loading