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

The nested BaseSettings raise a validation error when its attribute is uppercase.

Open you-n-g opened this issue 2 years ago • 2 comments

Initial Checks

  • [X] I have searched GitHub for a duplicate issue and I'm sure this is something new
  • [X] I have searched Google & StackOverflow for a solution and couldn't find anything
  • [X] I have read and followed the docs and still think this is a bug
  • [X] I am confident that the issue is with pydantic (not my code, or another library in the ecosystem like FastAPI or mypy)

Description

The nested BaseSettings raise a validation error when its attribute is uppercase. But it is OK when its attribute is lowercase. You can reproduce the error with the example code below.

I didn't find any docs about how the upper case affects the behavior of pydantic.

Did I miss anything?

Thanks

Example Code

# The following code will raise error...
from pydantic import BaseSettings

class Nested(BaseSettings):
    GOOD_BOY: str = "james"

class MyConfig(BaseSettings):
    nested: Nested = Nested()


    class Config:
        # env_prefix = "MY_"  # the prefix can't be mixed with the `env_nested_delimiter`
        env_nested_delimiter = '__'


os.environ["NESTED__GOOD_BOY"] = "env_boy"
config = MyConfig()
print(config)



# The following code will run correctly.

from pydantic import BaseSettings




class Nested(BaseSettings):
    good_boy: str = "james"



class MyConfig(BaseSettings):
    nested: Nested = Nested()


    class Config:
        # env_prefix = "MY_"  # the prefix can't be mixed with the `env_nested_delimiter`
        env_nested_delimiter = '__'



os.environ["NESTED__GOOD_BOY"] = "env_boy"
config = MyConfig()
print(config)

# The only difference is that its attribute is uppercase.

Python, Pydantic & OS Version

pydantic version: 1.9.2
            pydantic compiled: True
                 install path: /sdc/home/xiaoyang/miniconda3/lib/python3.9/site-packages/pydantic
               python version: 3.9.7 (default, Sep 16 2021, 13:09:58)  [GCC 7.5.0]
                     platform: Linux-5.4.0-126-generic-x86_64-with-glibc2.27
     optional deps. installed: ['dotenv', 'typing-extensions']

Affected Components

you-n-g avatar Oct 09 '22 04:10 you-n-g

any specific reason why your Nested class also inherits from BaseSettings?

dsal3389 avatar Oct 11 '22 23:10 dsal3389

I think this is because all env vars are converted to lower case unless you have case_sensitive = True.

You can fix this with either:

  • class Nested(BaseModel) instead of class Nested(BaseSettings)
  • or, you can set case_sensitive = True in config on MyConfig.Config

I agree in V2 that in "case insensitive" should mean case insensitive.

samuelcolvin avatar Oct 12 '22 09:10 samuelcolvin

any specific reason why your Nested class also inherits from BaseSettings?

Hi, @dsal3389 , @samuelcolvin Thanks for your replies.

I didn't know that the nested setting should inherit from BaseModel instead of BaseSettings. Thanks for letting me know about the more standard implementation.

And the following code still does not work

import os
from pydantic import BaseSettings, BaseModel

class Nested(BaseModel):
    GOOD_BOY: str = "james"

class MyConfig(BaseSettings):
    nested: Nested = Nested()


    class Config:
        # env_prefix = "MY_"  # the prefix can't be mixed with the `env_nested_delimiter`
        env_nested_delimiter = '__'

os.environ["NESTED__GOOD_BOY"] = "env_boy_v03"
config = MyConfig()
print(config)

The case_sensitive = True works!

you-n-g avatar Nov 14 '22 02:11 you-n-g

It got fixed in pydantic-settings

FYI, pydantic-settings now is a separate package and is in alpha state. you can install it by pip install pydantic-settings --pre and test it.

Here is your example in pydantic-settings:

import os

from pydantic import BaseModel, ConfigDict
from pydantic_settings import BaseSettings


class Nested(BaseModel):
    GOOD_BOY: str = "james"


class MyConfig(BaseSettings):
    nested: Nested = Nested()

    model_config = ConfigDict(env_nested_delimiter='__')

os.environ["NESTED__GOOD_BOY"] = "env_boy_v03"
config = MyConfig()
print(config)

hramezani avatar Apr 28 '23 11:04 hramezani