-
-
Notifications
You must be signed in to change notification settings - Fork 108
Description
Hi,
While I genuinely like pydantic and pydantic settings for many use-cases, I have been batteling it for a few hour now, just to discover that there seems to be non-deterministic behaviour when resolving fields.
It is a bit hard to put in words, so let's provide the basic schema first:
from pydantic import Field, AliasChoices
from pydantic_settings import BaseSettings, SettingsConfigDict
class NestedSettings(BaseSettings):
model_config = SettingsConfigDict(
validate_by_alias=True,
validate_by_name=True,
extra="allow",
)
user: str = Field(validation_alias=AliasChoices("user", "db_user"))
class AppSettings(BaseSettings):
model_config = SettingsConfigDict(
env_file_encoding="utf-8",
env_nested_delimiter="__",
env_nested_max_split=2,
)
connection: NestedSettingsand here is a corresponding unit test which essentially contains 4 times the same test case:
import os
from schemata import AppSettings
class TestMWE:
def test_1(self):
os.environ["DB_USER"] = "inner"
os.environ["CONNECTION__DB_USER"] = "outer"
settings = AppSettings(**{"connection": {"user": "explicit"}})
assert settings.connection.user == "explicit"
def test_2(self):
os.environ["DB_USER"] = "inner"
os.environ["CONNECTION__DB_USER"] = "outer"
settings = AppSettings(**{"connection": {"user": "explicit"}})
assert settings.connection.user == "outer"
def test_3(self):
os.environ["DB_USER"] = "inner3"
os.environ["CONNECTION__DB_USER"] = "outer3"
settings = AppSettings(**{"connection": {"user": "explicit3"}})
assert settings.connection.user == "explicit3"
def test_4(self):
os.environ["DB_USER"] = "inner4"
os.environ["CONNECTION__DB_USER"] = "outer4"
settings = AppSettings(**{"connection": {"user": "explicit4"}})
assert settings.connection.user == "outer4"
Now I execute this test in a docker container with --build --rm to ensure a fresh and clean environment, but what I get is, that the test cases that fail flip from time to time. Here are 2 subsequent executions:
$ docker compose run --build --rm test test_mwe.py
[+] Building 1.8s (13/13) FINISHED docker:default
=================================================================== test session starts ====================================================================
platform linux -- Python 3.11.13, pytest-8.4.0, pluggy-1.6.0
configfile: pyproject.toml
plugins: anyio-4.9.0
collected 4 items
test_mwe.py .F.F [100%]
========================================================================= FAILURES =========================================================================
______________________________________________________________________ TestMWE.test_2 ______________________________________________________________________
self = <py_tests.test_mwe.TestMWE object at 0x74022ba6bd50>
def test_2(self):
os.environ["DB_USER"] = "inner"
os.environ["CONNECTION__DB_USER"] = "outer"
settings = AppSettings(**{"connection": {"user": "explicit"}})
> assert settings.connection.user == "outer"
E AssertionError: assert 'explicit' == 'outer'
E
E - outer
E + explicit
py_tests/test_mwe.py:15: AssertionError
______________________________________________________________________ TestMWE.test_4 ______________________________________________________________________
self = <py_tests.test_mwe.TestMWE object at 0x74022ba009d0>
def test_4(self):
os.environ["DB_USER"] = "inner4"
os.environ["CONNECTION__DB_USER"] = "outer4"
settings = AppSettings(**{"connection": {"user": "explicit4"}})
> assert settings.connection.user == "outer4"
E AssertionError: assert 'explicit4' == 'outer4'
E
E - outer4
E + explicit4
py_tests/test_mwe.py:27: AssertionError
===================================================================== warnings summary =====================================================================
../usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:198
/usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:198: UserWarning: Field name "schema" in "DBConnectionSettings" shadows an attribute in parent "BaseSettings"
warnings.warn(
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================================================= short test summary info ==================================================================
FAILED py_tests/test_mwe.py::TestMWE::test_2 - AssertionError: assert 'explicit' == 'outer'
FAILED py_tests/test_mwe.py::TestMWE::test_4 - AssertionError: assert 'explicit4' == 'outer4'
========================================================== 2 failed, 2 passed, 1 warning in 0.73s ==========================================================
$ docker compose run --build --rm test py_tests/test_mwe.py
[+] Building 1.2s (13/13) FINISHED docker:default
=================================================================== test session starts ====================================================================
platform linux -- Python 3.11.13, pytest-8.4.0, pluggy-1.6.0
configfile: pyproject.toml
plugins: anyio-4.9.0
collected 4 items
py_tests/test_mwe.py F.F. [100%]
========================================================================= FAILURES =========================================================================
______________________________________________________________________ TestMWE.test_1 ______________________________________________________________________
self = <py_tests.test_mwe.TestMWE object at 0x70986c834a10>
def test_1(self):
os.environ["DB_USER"] = "inner"
os.environ["CONNECTION__DB_USER"] = "outer"
settings = AppSettings(**{"connection": {"user": "explicit"}})
> assert settings.connection.user == "explicit"
E AssertionError: assert 'outer' == 'explicit'
E
E - explicit
E + outer
py_tests/test_mwe.py:9: AssertionError
______________________________________________________________________ TestMWE.test_3 ______________________________________________________________________
self = <py_tests.test_mwe.TestMWE object at 0x70986b19ff90>
def test_3(self):
os.environ["DB_USER"] = "inner3"
os.environ["CONNECTION__DB_USER"] = "outer3"
settings = AppSettings(**{"connection": {"user": "explicit3"}})
> assert settings.connection.user == "explicit3"
E AssertionError: assert 'outer3' == 'explicit3'
E
E - explicit3
E + outer3
py_tests/test_mwe.py:21: AssertionError
===================================================================== warnings summary =====================================================================
../usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:198
/usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py:198: UserWarning: Field name "schema" in "DBConnectionSettings" shadows an attribute in parent "BaseSettings"
warnings.warn(
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================================================= short test summary info ==================================================================
FAILED py_tests/test_mwe.py::TestMWE::test_1 - AssertionError: assert 'outer' == 'explicit'
FAILED py_tests/test_mwe.py::TestMWE::test_3 - AssertionError: assert 'outer3' == 'explicit3'
========================================================== 2 failed, 2 passed, 1 warning in 0.68s ==========================================================As you can see, in the first execution, tests 2 and 4 fail, but in the second, test 1 and 3 fail. Please help me out here, this is driving me crazy :D
Cheers Enrico