Skip to content

Non-Deterministic Field Resolution #679

@enrico-stauss

Description

@enrico-stauss

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: NestedSettings

and 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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions