pydantic-settings
pydantic-settings copied to clipboard
Feature Request: Clearer errors with variable prefix included
$ python -c "import pydantic.utils; print(pydantic.utils.version_info())"
pydantic version: 1.3
pydantic compiled: False
install path: /Users/andrew/.virtualenvs/project_name/lib/python3.7/site-packages/pydantic
python version: 3.7.5 (default, Oct 19 2019, 01:20:12) [Clang 10.0.1 (clang-1001.0.46.4)]
platform: Darwin-18.7.0-x86_64-i386-64bit
optional deps. installed: ['devtools']
Feature Request
Thanks for a great library and for taking the time to read this!
When pydantic raises validation errors about settings, it would be nice if it would include the settings prefix, if specified.
Let's assume a basic set of settings where variables are meant to be specified in the process environment.
from pydantic import BaseSettings
class Settings(BaseSettings):
"""Define basic settings to be overridden by the environment"""
var_name: str
class Config:
env_prefix = "app_prefix"
If this class is instantiated with no changes to the environment, a Python traceback is printed with the following at the end
var_name
field required (type=value_error.missing)
The actual Validation object appears to be:
ValidationError(
model='Settings',
errors=[
{'loc': ('var_name',), 'msg': 'field required', 'type': 'value_error.missing'},
]
)
It would be nice if instead of specifying only the variable name it also included the env_prefix. What is most helpful is something like:
ERROR: Set Environment Variable APP_PREFIX_VAR_NAME
To print a nicer error message, I am currently doing the following:
from colorama import Fore, Style
from pydantic.error_wrappers import ValidationError
from .config import Settings
def main():
try:
settings = Settings()
except ValidationError as e:
prefix = Settings.Config.env_prefix
for error in e.errors():
if error["type"] == "value_error.missing":
for var in error["loc"]:
env_var = f"{prefix}{var}".upper()
message = f"ERROR: Set Environment Variable {env_var}"
print(Fore.RED + message + Style.RESET_ALL)
else:
print(f"{Fore.RED}{error}{Style.RESET_ALL}")
exit(-1)
if __name__ == "__main__":
main()
In general, I think it might be nice if ValidationError and/or ErrorWrapper was a little more extensible.
I was thinking about this because of how surprisingly awkward it was to get access to the specific values causing errors when using the parse_obj_as function to parse a typed container. (If there is an easy way to get access to the value causing an error, please let me know!)
That specific case is clearly pretty far afield of this feature request, and I am generally in support of this feature request if it can be implemented simply, but I bring it up because it seems to me like the right implementation could perhaps address both problems at the same time.
Agreed. Perhaps related to pydantic/pydantic#1094.
I also want to always add the value which failed validation to the error context even if it's not rendered. (Though I know that's not directly related to this issue)
I ran into the same concern, but unfortunately, the workaround proposed by @jambonrose cannot be used in my case since I have embedded models, being themselves BaseSettings with different prefixes. As illustrated in the documentation, the field definition of the parent is set to an instance of the sub-settings class, and thus importing the module is enough to raise the validation error.
I managed the situation by creating a custom BaseSettings, overriding the __init__ method this way :
class CustomSettings(BaseSettings):
""" Custom settings overriding the validation error handling to provide more explicit
error messages containing the environment variable names instead of the model
field ones.
"""
def __init__(__pydantic_self__, _env_file: Union[Path, str, None] = env_file_sentinel, **values: Any) -> None:
try:
super().__init__(**__pydantic_self__._build_values(values, _env_file=_env_file))
except ValidationError as e:
# patch error locations, replacing the field name by the expected environment variable
env_prefix = __pydantic_self__.__class__.Config.env_prefix
for err in e.errors():
env_var = f"{env_prefix}{err['loc'][0].upper()}"
err['loc'] = (env_var,)
raise
class Config:
error_msg_templates = {
'value_error.missing': 'environment variable is not set'
}
It is certainly not the cleanest way to do. In addition, it does not take aliases into account if any. But it can be a starting point for something more academic.
+1