Skip to content
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

Expose API for setting the global "show errors in reprs" flag #1118

Closed
wants to merge 1 commit into from
Closed
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
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
11 changes: 11 additions & 0 deletions python/pydantic_core/_pydantic_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ __all__ = [
'from_json',
'to_jsonable_python',
'list_all_errors',
'set_errors_include_url',
'TzInfo',
'validate_core_schema',
]
Expand Down Expand Up @@ -849,6 +850,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)