pydantic-settings icon indicating copy to clipboard operation
pydantic-settings copied to clipboard

Feature Request: Clearer errors with variable prefix included

Open jambonrose opened this issue 5 years ago • 4 comments

$ 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()

jambonrose avatar Jan 17 '20 19:01 jambonrose

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.

dmontagu avatar Jan 20 '20 21:01 dmontagu

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)

samuelcolvin avatar Jan 20 '20 21:01 samuelcolvin

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.

EricPobot avatar Jun 03 '20 15:06 EricPobot

+1

ondrados avatar May 15 '24 11:05 ondrados