Skip to content

Commit

Permalink
Add hide_input to ValidationError (pydantic#633)
Browse files Browse the repository at this point in the history
  • Loading branch information
hramezani authored May 25, 2023
1 parent 26fa27d commit 726ef5f
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 25 deletions.
5 changes: 4 additions & 1 deletion pydantic_core/_pydantic_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@ class SchemaError(Exception):
class ValidationError(ValueError):
@staticmethod
def from_exception_data(
title: str, errors: 'list[InitErrorDetails]', error_mode: Literal['python', 'json'] = 'python'
title: str,
errors: 'list[InitErrorDetails]',
error_mode: Literal['python', 'json'] = 'python',
hide_input_in_errors: bool = False,
) -> ValidationError:
"""
Provisory constructor for a Validation Error.
Expand Down
2 changes: 2 additions & 0 deletions pydantic_core/core_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class CoreConfig(TypedDict, total=False):
# the config options are used to customise serialization to JSON
ser_json_timedelta: Literal['iso8601', 'float'] # default: 'iso8601'
ser_json_bytes: Literal['utf8', 'base64'] # default: 'utf8'
# used to hide input data from ValidationError repr
hide_input_in_errors: bool


IncExCall: TypeAlias = 'set[int | str] | dict[int | str, IncExCall] | None'
Expand Down
9 changes: 5 additions & 4 deletions src/build_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ impl SchemaError {
match error {
ValError::LineErrors(raw_errors) => {
let line_errors = raw_errors.into_iter().map(|e| e.into_py(py)).collect();
let validation_error = ValidationError::new(line_errors, "Schema".to_object(py), ErrorMode::Python);
let validation_error =
ValidationError::new(line_errors, "Schema".to_object(py), ErrorMode::Python, false);
let schema_error = SchemaError(SchemaErrorEnum::ValidationError(validation_error));
match Py::new(py, schema_error) {
Ok(err) => PyErr::from_value(err.into_ref(py)),
Expand Down Expand Up @@ -177,21 +178,21 @@ impl SchemaError {
fn errors(&self, py: Python) -> PyResult<Py<PyList>> {
match &self.0 {
SchemaErrorEnum::Message(_) => Ok(PyList::empty(py).into_py(py)),
SchemaErrorEnum::ValidationError(error) => error.errors(py, false, true),
SchemaErrorEnum::ValidationError(error) => error.errors(py, false, false),
}
}

fn __str__(&self, py: Python) -> String {
match &self.0 {
SchemaErrorEnum::Message(message) => message.to_owned(),
SchemaErrorEnum::ValidationError(error) => error.display(py, Some("Invalid Schema:")),
SchemaErrorEnum::ValidationError(error) => error.display(py, Some("Invalid Schema:"), false),
}
}

fn __repr__(&self, py: Python) -> String {
match &self.0 {
SchemaErrorEnum::Message(message) => format!("SchemaError({message:?})"),
SchemaErrorEnum::ValidationError(error) => error.display(py, Some("Invalid Schema:")),
SchemaErrorEnum::ValidationError(error) => error.display(py, Some("Invalid Schema:"), false),
}
}
}
Expand Down
50 changes: 38 additions & 12 deletions src/errors/validation_exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,21 @@ pub struct ValidationError {
line_errors: Vec<PyLineError>,
error_mode: ErrorMode,
title: PyObject,
hide_input_in_errors: bool,
}

impl ValidationError {
pub fn new(line_errors: Vec<PyLineError>, title: PyObject, error_mode: ErrorMode) -> Self {
pub fn new(
line_errors: Vec<PyLineError>,
title: PyObject,
error_mode: ErrorMode,
hide_input_in_errors: bool,
) -> Self {
Self {
line_errors,
title,
error_mode,
hide_input_in_errors,
}
}

Expand All @@ -48,6 +55,7 @@ impl ValidationError {
error_mode: ErrorMode,
error: ValError,
outer_location: Option<LocItem>,
hide_input_in_errors: bool,
) -> PyErr {
match error {
ValError::LineErrors(raw_errors) => {
Expand All @@ -58,7 +66,7 @@ impl ValidationError {
.collect(),
None => raw_errors.into_iter().map(|e| e.into_py(py)).collect(),
};
let validation_error = Self::new(line_errors, title, error_mode);
let validation_error = Self::new(line_errors, title, error_mode, hide_input_in_errors);
match Py::new(py, validation_error) {
Ok(err) => PyErr::from_value(err.into_ref(py)),
Err(err) => err,
Expand All @@ -69,9 +77,15 @@ impl ValidationError {
}
}

pub fn display(&self, py: Python, prefix_override: Option<&'static str>) -> String {
pub fn display(&self, py: Python, prefix_override: Option<&'static str>, hide_input_in_errors: bool) -> String {
let url_prefix = get_url_prefix(py, include_url_env(py));
let line_errors = pretty_py_line_errors(py, &self.error_mode, self.line_errors.iter(), url_prefix);
let line_errors = pretty_py_line_errors(
py,
&self.error_mode,
self.line_errors.iter(),
url_prefix,
hide_input_in_errors,
);
if let Some(prefix) = prefix_override {
format!("{prefix}\n{line_errors}")
} else {
Expand Down Expand Up @@ -124,18 +138,21 @@ impl<'a> IntoPy<ValError<'a>> for ValidationError {
#[pymethods]
impl ValidationError {
#[staticmethod]
#[pyo3(signature = (title, line_errors, error_mode=None, hide_input_in_errors=false))]
fn from_exception_data(
py: Python,
title: PyObject,
line_errors: &PyList,
error_mode: Option<&str>,
hide_input_in_errors: bool,
) -> PyResult<Py<Self>> {
Py::new(
py,
Self {
line_errors: line_errors.iter().map(PyLineError::try_from).collect::<PyResult<_>>()?,
title,
error_mode: ErrorMode::try_from(error_mode)?,
hide_input_in_errors,
},
)
}
Expand Down Expand Up @@ -210,7 +227,7 @@ impl ValidationError {
}

fn __repr__(&self, py: Python) -> String {
self.display(py, None)
self.display(py, None, self.hide_input_in_errors)
}

fn __str__(&self, py: Python) -> String {
Expand Down Expand Up @@ -238,9 +255,10 @@ pub fn pretty_py_line_errors<'a>(
error_mode: &ErrorMode,
line_errors_iter: impl Iterator<Item = &'a PyLineError>,
url_prefix: Option<&str>,
hide_input_in_errors: bool,
) -> String {
line_errors_iter
.map(|i| i.pretty(py, error_mode, url_prefix))
.map(|i| i.pretty(py, error_mode, url_prefix, hide_input_in_errors))
.collect::<Result<Vec<_>, _>>()
.unwrap_or_else(|err| vec![format!("[error formatting line errors: {err}]")])
.join("\n")
Expand Down Expand Up @@ -349,7 +367,13 @@ impl PyLineError {
Ok(dict.into_py(py))
}

fn pretty(&self, py: Python, error_mode: &ErrorMode, url_prefix: Option<&str>) -> Result<String, fmt::Error> {
fn pretty(
&self,
py: Python,
error_mode: &ErrorMode,
url_prefix: Option<&str>,
hide_input_in_errors: bool,
) -> Result<String, fmt::Error> {
let mut output = String::with_capacity(200);
write!(output, "{}", self.location)?;

Expand All @@ -359,12 +383,14 @@ impl PyLineError {
};
write!(output, " {message} [type={}", self.error_type.type_string())?;

let input_value = self.input_value.as_ref(py);
let input_str = safe_repr(input_value);
truncate_input_value!(output, input_str);
if !hide_input_in_errors {
let input_value = self.input_value.as_ref(py);
let input_str = safe_repr(input_value);
truncate_input_value!(output, input_str);

if let Ok(type_) = input_value.get_type().name() {
write!(output, ", input_type={type_}")?;
if let Ok(type_) = input_value.get_type().name() {
write!(output, ", input_type={type_}")?;
}
}
if let Some(url_prefix) = url_prefix {
match self.error_type {
Expand Down
31 changes: 30 additions & 1 deletion src/validators/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,36 @@ pub struct FunctionWrapValidator {
name: String,
is_field_validator: bool,
info_arg: bool,
hide_input_in_errors: bool,
}

impl_build!(FunctionWrapValidator, "function-wrap");
impl BuildValidator for FunctionWrapValidator {
const EXPECTED_TYPE: &'static str = "function-wrap";

fn build(
schema: &PyDict,
config: Option<&PyDict>,
definitions: &mut DefinitionsBuilder<CombinedValidator>,
) -> PyResult<CombinedValidator> {
let py = schema.py();
let validator = build_validator(schema.get_as_req(intern!(py, "schema"))?, config, definitions)?;
let (is_field_validator, info_arg, function) = destructure_function_schema(schema)?;
let hide_input_in_errors: bool = config.get_as(intern!(py, "hide_input_in_errors"))?.unwrap_or(false);
Ok(Self {
validator: Box::new(validator),
func: function.into_py(py),
config: match config {
Some(c) => c.into(),
None => py.None(),
},
name: format!("function-wrap[{}()]", function_name(function)?),
is_field_validator,
info_arg,
hide_input_in_errors,
}
.into())
}
}

impl FunctionWrapValidator {
fn _validate<'s, 'data>(
Expand Down Expand Up @@ -301,6 +328,7 @@ impl Validator for FunctionWrapValidator {
definitions,
extra,
recursion_guard,
self.hide_input_in_errors,
),
};
self._validate(
Expand Down Expand Up @@ -329,6 +357,7 @@ impl Validator for FunctionWrapValidator {
definitions,
extra,
recursion_guard,
self.hide_input_in_errors,
),
updated_field_name: field_name.to_string(),
updated_field_value: field_value.to_object(py),
Expand Down
46 changes: 40 additions & 6 deletions src/validators/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct GeneratorValidator {
min_length: Option<usize>,
max_length: Option<usize>,
name: String,
hide_input_in_errors: bool,
}

impl BuildValidator for GeneratorValidator {
Expand All @@ -33,11 +34,15 @@ impl BuildValidator for GeneratorValidator {
Some(ref v) => format!("{}[{}]", Self::EXPECTED_TYPE, v.get_name()),
None => format!("{}[any]", Self::EXPECTED_TYPE),
};
let hide_input_in_errors: bool = config
.get_as(pyo3::intern!(schema.py(), "hide_input_in_errors"))?
.unwrap_or(false);
Ok(Self {
item_validator,
name,
min_length: schema.get_as(pyo3::intern!(schema.py(), "min_length"))?,
max_length: schema.get_as(pyo3::intern!(schema.py(), "max_length"))?,
hide_input_in_errors,
}
.into())
}
Expand All @@ -53,16 +58,24 @@ impl Validator for GeneratorValidator {
recursion_guard: &'s mut RecursionGuard,
) -> ValResult<'data, PyObject> {
let iterator = input.validate_iter()?;
let validator = self
.item_validator
.as_ref()
.map(|v| InternalValidator::new(py, "ValidatorIterator", v, definitions, extra, recursion_guard));
let validator = self.item_validator.as_ref().map(|v| {
InternalValidator::new(
py,
"ValidatorIterator",
v,
definitions,
extra,
recursion_guard,
self.hide_input_in_errors,
)
});

let v_iterator = ValidatorIterator {
iterator,
validator,
min_length: self.min_length,
max_length: self.max_length,
hide_input_in_errors: self.hide_input_in_errors,
};
Ok(v_iterator.into_py(py))
}
Expand Down Expand Up @@ -98,6 +111,7 @@ struct ValidatorIterator {
validator: Option<InternalValidator>,
min_length: Option<usize>,
max_length: Option<usize>,
hide_input_in_errors: bool,
}

#[pymethods]
Expand All @@ -109,6 +123,7 @@ impl ValidatorIterator {
fn __next__(mut slf: PyRefMut<'_, Self>, py: Python) -> PyResult<Option<PyObject>> {
let min_length = slf.min_length;
let max_length = slf.max_length;
let hide_input_in_errors = slf.hide_input_in_errors;
let Self {
validator, iterator, ..
} = &mut *slf;
Expand All @@ -133,6 +148,7 @@ impl ValidatorIterator {
ErrorMode::Python,
val_error,
None,
hide_input_in_errors,
));
}
}
Expand All @@ -157,6 +173,7 @@ impl ValidatorIterator {
ErrorMode::Python,
val_error,
None,
hide_input_in_errors,
));
}
}
Expand Down Expand Up @@ -203,6 +220,7 @@ pub struct InternalValidator {
self_instance: Option<PyObject>,
recursion_guard: RecursionGuard,
validation_mode: InputType,
hide_input_in_errors: bool,
}

impl fmt::Debug for InternalValidator {
Expand All @@ -219,6 +237,7 @@ impl InternalValidator {
definitions: &[CombinedValidator],
extra: &Extra,
recursion_guard: &RecursionGuard,
hide_input_in_errors: bool,
) -> Self {
Self {
name: name.to_string(),
Expand All @@ -230,6 +249,7 @@ impl InternalValidator {
self_instance: extra.self_instance.map(|d| d.into_py(py)),
recursion_guard: recursion_guard.clone(),
validation_mode: extra.mode,
hide_input_in_errors,
}
}

Expand Down Expand Up @@ -261,7 +281,14 @@ impl InternalValidator {
&mut self.recursion_guard,
)
.map_err(|e| {
ValidationError::from_val_error(py, self.name.to_object(py), ErrorMode::Python, e, outer_location)
ValidationError::from_val_error(
py,
self.name.to_object(py),
ErrorMode::Python,
e,
outer_location,
self.hide_input_in_errors,
)
})
}

Expand All @@ -286,7 +313,14 @@ impl InternalValidator {
self.validator
.validate(py, input, &extra, &self.definitions, &mut self.recursion_guard)
.map_err(|e| {
ValidationError::from_val_error(py, self.name.to_object(py), ErrorMode::Python, e, outer_location)
ValidationError::from_val_error(
py,
self.name.to_object(py),
ErrorMode::Python,
e,
outer_location,
self.hide_input_in_errors,
)
})
}
}
Loading

0 comments on commit 726ef5f

Please sign in to comment.