Skip to content

Commit

Permalink
Expose API for setting the global "show errors in reprs" flag
Browse files Browse the repository at this point in the history
  • Loading branch information
akx committed Dec 12, 2023
1 parent 7fa450d commit b6f8aec
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 10 deletions.
2 changes: 2 additions & 0 deletions python/pydantic_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
ValidationError,
__version__,
from_json,
set_errors_include_url,
to_json,
to_jsonable_python,
validate_core_schema,
Expand Down Expand Up @@ -65,6 +66,7 @@
'TzInfo',
'to_json',
'from_json',
'set_errors_include_url',
'to_jsonable_python',
'validate_core_schema',
]
Expand Down
10 changes: 10 additions & 0 deletions python/pydantic_core/_pydantic_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,16 @@ def list_all_errors() -> list[ErrorTypeInfo]:
Returns:
A list of `ErrorTypeInfo` typed dicts.
"""

def set_errors_include_url(flag: bool) -> None:
"""
Set whether `repr`s of errors should include URLs to documentation on the error.
Defaults to `true` unless the `PYDANTIC_ERRORS_OMIT_URL` is set.
Args:
flag: Whether to include URLs in error `repr`s.
"""
@final
class TzInfo(datetime.tzinfo):
def tzname(self, _dt: datetime.datetime | None) -> str | None: ...
Expand Down
2 changes: 1 addition & 1 deletion src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod value_exception;
pub use self::line_error::{AsErrorValue, InputValue, ValError, ValLineError, ValResult};
pub use self::location::{AsLocItem, LocItem};
pub use self::types::{list_all_errors, ErrorType, ErrorTypeDefaults, Number};
pub use self::validation_exception::ValidationError;
pub use self::validation_exception::{set_errors_include_url, ValidationError};
pub use self::value_exception::{PydanticCustomError, PydanticKnownError, PydanticOmit, PydanticUseDefault};

pub fn py_err_string(py: Python, err: PyErr) -> String {
Expand Down
24 changes: 16 additions & 8 deletions src/errors/validation_exception.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::cell::RefCell;
use std::fmt;
use std::fmt::{Display, Write};
use std::str::from_utf8;

use pyo3::exceptions::{PyKeyError, PyTypeError, PyValueError};
use pyo3::ffi;
use pyo3::intern;
use pyo3::once_cell::GILOnceCell;
use pyo3::prelude::*;
use pyo3::sync::{GILOnceCell, GILProtected};
use pyo3::types::{PyDict, PyList, PyString};
use serde::ser::{Error, SerializeMap, SerializeSeq};
use serde::{Serialize, Serializer};
Expand Down Expand Up @@ -85,7 +86,7 @@ impl ValidationError {
}

pub fn display(&self, py: Python, prefix_override: Option<&'static str>, hide_input: bool) -> String {
let url_prefix = get_url_prefix(py, include_url_env(py));
let url_prefix = get_url_prefix(py, include_url(py));
let line_errors = pretty_py_line_errors(py, self.input_type, self.line_errors.iter(), url_prefix, hide_input);
if let Some(prefix) = prefix_override {
format!("{prefix}\n{line_errors}")
Expand Down Expand Up @@ -191,17 +192,24 @@ impl ValidationError {
}
}

static URL_ENV_VAR: GILOnceCell<bool> = GILOnceCell::new();
static INCLUDE_URL_FLAG: GILProtected<RefCell<Option<bool>>> = GILProtected::new(RefCell::new(None));

fn _get_include_url_env() -> bool {
match std::env::var("PYDANTIC_ERRORS_OMIT_URL") {
fn include_url(py: Python) -> bool {
if let Some(flag) = *INCLUDE_URL_FLAG.get(py).borrow() {
return flag;
}
// If uninitialized, initialize from environment variable.
let flag = match std::env::var("PYDANTIC_ERRORS_OMIT_URL") {
Ok(val) => val.is_empty(),
Err(_) => true,
}
};
INCLUDE_URL_FLAG.get(py).borrow_mut().replace(flag);
flag
}

fn include_url_env(py: Python) -> bool {
*URL_ENV_VAR.get_or_init(py, _get_include_url_env)
#[pyfunction]
pub fn set_errors_include_url(py: Python, flag: bool) {
INCLUDE_URL_FLAG.get(py).borrow_mut().replace(flag);
}

static URL_PREFIX: GILOnceCell<String> = GILOnceCell::new();
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ pub use self::url::{PyMultiHostUrl, PyUrl};
pub use argument_markers::{ArgsKwargs, PydanticUndefinedType};
pub use build_tools::SchemaError;
pub use errors::{
list_all_errors, PydanticCustomError, PydanticKnownError, PydanticOmit, PydanticUseDefault, ValidationError,
list_all_errors, set_errors_include_url, PydanticCustomError, PydanticKnownError, PydanticOmit, PydanticUseDefault,
ValidationError,
};
pub use serializers::{
to_json, to_jsonable_python, PydanticSerializationError, PydanticSerializationUnexpectedValue, SchemaSerializer,
Expand Down Expand Up @@ -110,6 +111,7 @@ fn _pydantic_core(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(from_json, m)?)?;
m.add_function(wrap_pyfunction!(to_jsonable_python, m)?)?;
m.add_function(wrap_pyfunction!(list_all_errors, m)?)?;
m.add_function(wrap_pyfunction!(set_errors_include_url, m)?)?;
m.add_function(wrap_pyfunction!(validate_core_schema, m)?)?;
Ok(())
}
12 changes: 12 additions & 0 deletions tests/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
SchemaValidator,
ValidationError,
core_schema,
set_errors_include_url,
)
from pydantic_core._pydantic_core import list_all_errors

Expand Down Expand Up @@ -1074,3 +1075,14 @@ def test_hide_input_in_json() -> None:

for error in exc_info.value.errors(include_input=False):
assert 'input' not in error


def test_hide_url_in_repr() -> None:
s = SchemaValidator({'type': 'int'})
with pytest.raises(ValidationError) as exc_info:
s.validate_python('definitely not an int')

set_errors_include_url(False)
assert 'https' not in repr(exc_info.value)
set_errors_include_url(True)
assert 'https' in repr(exc_info.value)

0 comments on commit b6f8aec

Please sign in to comment.