Skip to content

2.2.0 broke support for nested BaseSettings with base type (e.g. using ABC) #241

Closed
@moonrail

Description

@moonrail

Hello altogether,

change #204 is a breaking change. It introduces a dict-cast that leads to nested copy-by-value for BaseSettings instances. In 2.1.0 it was copy-by-reference.

See this line: https://github.com/pydantic/pydantic-settings/pull/204/files#diff-b4b68ace3cb0cf2820d1d882735748b675a379c39463ccc89700a9618a80a2a2R124

This breaks any use-case with a base class/type and/or abc classes, where the type is not explicitly known beforehand.
E.g. for pluggable authentication:

from abc import ABC, abstractmethod
from pydantic import SecretStr, HttpUrl
from pydantic_settings import BaseSettings
from typing import Type


class BaseAuth(ABC, BaseSettings):
    @property
    @abstractmethod
    def token(self) -> str:
        """returns authentication token for XYZ"""
        pass


class CustomAuth(BaseAuth):
    url: HttpUrl
    username: str
    password: SecretStr

    _token: SecretStr = None

    @property
    def token(self):
        ...  # (re)fetch token
        return self._token.get_secret_value()


class Settings(BaseSettings):
    auth: Type[BaseAuth]


s = Settings(
    auth=CustomAuth(
        url='https://127.0.0.1',
        username='some-username',
        password='some-password'
    )
)
print(s.auth)
print(type(s.auth))

Upon execution you'll receive following ValidationException:

Traceback (most recent call last):
  File "/tmp/test_pydantic_settings_nesting.py", line 32, in <module>
    s = Settings(
        ^^^^^^^^^
  File "/tmp/venv/lib/python3.11/site-packages/pydantic_settings/main.py", line 85, in __init__
    super().__init__(
  File "/tmp/venv/lib/python3.11/site-packages/pydantic/main.py", line 171, in __init__
    self.__pydantic_validator__.validate_python(data, self_instance=self)
pydantic_core._pydantic_core.ValidationError: 1 validation error for Settings
auth
  Input should be a subclass of BaseAuth [type=is_subclass_of, input_value={'url': Url('https://127....SecretStr('**********')}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.6/v/is_subclass_of

From this POV 2.2.0 should be 3.0.0, as this is a Breaking Change. Moving from copy-by-reference to copy-by-value with casting is a change that can have a lot of impact in an interfacing-focused library.

As a side note: The amount of breaking changes in Minor-Releases across the pydantic project increased in the last year. We have more and more debugging sessions regarding it and are scratching our heads, if and how we are possibly "holding it wrong" or its something out of our control and pydantic is not as stable as it should be...

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions