Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/providers/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,19 @@ the container will call ``config.from_pydantic()`` automatically:
if __name__ == "__main__":
container = Container() # Config is loaded from Settings()

In addition, if you need the pydantic instance to be initialized on use, you can provide ``pydantic_settings.BaseSettings`` type instead.
The container will initialize a pydantic instance on load without kwargs.

.. code-block:: python
:emphasize-lines: 3

class Container(containers.DeclarativeContainer):

config = providers.Configuration(pydantic_settings=[Settings])


if __name__ == "__main__":
container = Container() # Config is loaded from Settings instance that is initialized

.. note::

Expand Down
20 changes: 9 additions & 11 deletions src/dependency_injector/providers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ except ImportError:
yaml = None

try:
import pydantic
from pydantic_settings import BaseSettings as PydanticSettings
except ImportError:
pydantic = None
try:
from pydantic import BaseSettings as PydanticSettings
except ImportError:
PydanticSettings = Any

from . import resources

Expand Down Expand Up @@ -272,7 +275,7 @@ class Configuration(Object[Any]):
ini_files: Optional[_Iterable[Union[Path, str]]] = None,
yaml_files: Optional[_Iterable[Union[Path, str]]] = None,
json_files: Optional[_Iterable[Union[Path, str]]] = None,
pydantic_settings: Optional[_Iterable[PydanticSettings]] = None,
pydantic_settings: Optional[_Iterable[Union[PydanticSettings, Type[PydanticSettings]]]] = None,
) -> None: ...
def __enter__(self) -> _Self: ...
def __exit__(self, *exc_info: Any) -> None: ...
Expand All @@ -292,8 +295,8 @@ class Configuration(Object[Any]):
def set_yaml_files(self, files: _Iterable[Union[Path, str]]) -> _Self: ...
def get_json_files(self) -> _List[Union[Path, str]]: ...
def set_json_files(self, files: _Iterable[Union[Path, str]]) -> _Self: ...
def get_pydantic_settings(self) -> _List[PydanticSettings]: ...
def set_pydantic_settings(self, settings: _Iterable[PydanticSettings]) -> _Self: ...
def get_pydantic_settings(self) -> _List[Union[PydanticSettings, Type[PydanticSettings]]]: ...
def set_pydantic_settings(self, settings: _Iterable[Union[PydanticSettings, Type[PydanticSettings]]]) -> _Self: ...
def load(self, required: bool = False, envs_required: bool = False) -> None: ...
def get(self, selector: str) -> Any: ...
def set(self, selector: str, value: Any) -> OverridingContext[P]: ...
Expand All @@ -319,7 +322,7 @@ class Configuration(Object[Any]):
envs_required: bool = False,
) -> None: ...
def from_pydantic(
self, settings: PydanticSettings, required: bool = False, **kwargs: Any
self, settings: Union[PydanticSettings, Type[PydanticSettings]], required: bool = False, **kwargs: Any
) -> None: ...
def from_dict(self, options: _Dict[str, Any], required: bool = False) -> None: ...
def from_env(
Expand Down Expand Up @@ -630,8 +633,3 @@ if yaml:

else:
class YamlLoader: ...

if pydantic:
PydanticSettings = pydantic.BaseSettings
else:
PydanticSettings = Any
5 changes: 1 addition & 4 deletions src/dependency_injector/providers.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,7 @@ cdef dict pydantic_settings_to_dict(settings, dict kwargs):
)

if isinstance(settings, type) and issubclass(settings, PydanticSettings):
raise Error(
"Got settings class, but expect instance: "
"instead \"{0}\" use \"{0}()\"".format(settings.__name__)
)
settings = settings()

if not isinstance(settings, PydanticSettings):
raise Error(
Expand Down
16 changes: 11 additions & 5 deletions tests/typing/configuration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Any, Dict
from typing import Any, Dict, Type
from typing_extensions import assert_type

from pydantic_settings import BaseSettings as PydanticSettings
Expand Down Expand Up @@ -33,6 +33,8 @@

config2.from_pydantic(PydanticSettings())

config2.from_pydantic(PydanticSettings)

# Test 3: to check as_*() methods
config3 = providers.Configuration()
int3 = config3.option.as_int()
Expand Down Expand Up @@ -80,14 +82,18 @@
)
config5_pydantic.set_pydantic_settings([PydanticSettings()])

# NOTE: Using assignment since PydanticSettings is context-sensitive: conditional on whether pydantic is installed
config5_pydantic_settings: list[PydanticSettings] = (
config5_pydantic.get_pydantic_settings()
)
config5_pydantic_settings = config5_pydantic.get_pydantic_settings()

assert_type(config5_pydantic_settings, list[PydanticSettings | Type[PydanticSettings]])

# Test 6: to check init arguments
config6 = providers.Configuration(
name="config",
strict=True,
default={},
)

# Test 7: pydantic class
config7_pydantic_class = providers.Configuration(
pydantic_settings=[PydanticSettings]
)
26 changes: 14 additions & 12 deletions tests/unit/providers/configuration/test_from_pydantic_py36.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,21 +168,23 @@ def test_option_not_instance_of_settings(config):


def test_subclass_instead_of_instance(config):
with raises(errors.Error) as error:
config.from_pydantic(Settings1)
assert error.value.args[0] == (
"Got settings class, but expect instance: "
"instead \"Settings1\" use \"Settings1()\""
)
config.from_pydantic(Settings1)

assert config() == {"section1": {"value1": 1}, "section2": {"value2": 2}}
assert config.section1() == {"value1": 1}
assert config.section1.value1() == 1
assert config.section2() == {"value2": 2}
assert config.section2.value2() == 2


def test_option_subclass_instead_of_instance(config):
with raises(errors.Error) as error:
config.option.from_pydantic(Settings1)
assert error.value.args[0] == (
"Got settings class, but expect instance: "
"instead \"Settings1\" use \"Settings1()\""
)
config.option.from_pydantic(Settings1)

assert config.option() == {"section1": {"value1": 1}, "section2": {"value2": 2}}
assert config.option.section1() == {"value1": 1}
assert config.option.section1.value1() == 1
assert config.option.section2() == {"value2": 2}
assert config.option.section2.value2() == 2


@mark.usefixtures("no_pydantic_module_installed")
Expand Down