From 5c661f4c353f77f54ecc26f37f4bd670ee92d46b Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 15 Dec 2023 08:26:01 +0200 Subject: [PATCH] Rework the `PYDANTIC_ERRORS_INCLUDE_URL` environment variable and document it Refs https://github.com/pydantic/pydantic-core/pull/1118#issuecomment-1854040572 --- python/pydantic_core/_pydantic_core.pyi | 12 +++++++++ src/errors/validation_exception.rs | 12 +++++++-- tests/test_errors.py | 34 +++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/python/pydantic_core/_pydantic_core.pyi b/python/pydantic_core/_pydantic_core.pyi index 382a6c804..b8c1f4e94 100644 --- a/python/pydantic_core/_pydantic_core.pyi +++ b/python/pydantic_core/_pydantic_core.pyi @@ -787,6 +787,18 @@ class ValidationError(ValueError): a JSON string. """ + def __repr__(self) -> str: + """ + A string representation of the validation error. + + Whether or not documentation URLs are included in the repr is controlled by the + environment variable `PYDANTIC_ERRORS_INCLUDE_URL` being set to `1` or + `true`; by default, URLs are shown. + + Due to implementation details, this environment variable can only be set once, + before the first validation error is created. + """ + @final class PydanticCustomError(ValueError): def __new__( diff --git a/src/errors/validation_exception.rs b/src/errors/validation_exception.rs index e77b21974..8921b4bf2 100644 --- a/src/errors/validation_exception.rs +++ b/src/errors/validation_exception.rs @@ -194,8 +194,16 @@ impl ValidationError { static URL_ENV_VAR: GILOnceCell = GILOnceCell::new(); fn _get_include_url_env() -> bool { - match std::env::var("PYDANTIC_ERRORS_OMIT_URL") { - Ok(val) => val.is_empty(), + if std::env::var_os("PYDANTIC_ERRORS_OMIT_URL").is_some() { + // This is ugly (and is interposed weirdly into a printed traceback), but + // there probably isn't a much better way to surface a deprecation warning + // from so deep in the code. Hopefully there hadn't been too many users + // using the undocumented environment variable who will need to suffer + // the ugliness, and when they do, they'll act accordingly. + eprintln!("_pydantic_core: PYDANTIC_ERRORS_OMIT_URL is deprecated, use PYDANTIC_ERRORS_INCLUDE_URL instead"); + } + match std::env::var("PYDANTIC_ERRORS_INCLUDE_URL") { + Ok(val) => val == "1" || val.to_lowercase() == "true", Err(_) => true, } } diff --git a/tests/test_errors.py b/tests/test_errors.py index 88dcace8f..404d3c35f 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -1,6 +1,8 @@ import enum +import os import pickle import re +import subprocess import sys from decimal import Decimal from typing import Any, Optional @@ -1089,3 +1091,35 @@ def test_validation_error_pickle() -> None: original = exc_info.value roundtripped = pickle.loads(pickle.dumps(original)) assert original.errors() == roundtripped.errors() + + +@pytest.mark.skipif(sys.platform == 'emscripten', reason='no subprocesses on emscripten') +@pytest.mark.parametrize( + ('include_url_value', 'expected_to_have_url'), + [ + (None, True), + ('1', True), + ('True', True), + ('no', False), + ('0', False), + ], +) +def test_include_errors_envvar(include_url_value, expected_to_have_url) -> None: + # Since `PYDANTIC_ERRORS_INCLUDE_URL` can only be set + # before a single import of `pydantic` we need to + # test this in a separate process. + code = "import pydantic_core; pydantic_core.SchemaValidator({'type': 'int'}).validate_python('ooo')" + env = os.environ.copy() + if include_url_value is not None: + env['PYDANTIC_ERRORS_INCLUDE_URL'] = include_url_value + env['PYDANTIC_ERRORS_OMIT_URL'] = 'foo' # this is ignored but should show a deprecation warning + result = subprocess.run( + [sys.executable, '-c', code], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding='utf-8', + env=env, + ) + assert result.returncode == 1 + assert 'PYDANTIC_ERRORS_OMIT_URL is deprecated' in result.stdout + assert ('https://errors.pydantic.dev' in result.stdout) == expected_to_have_url