Added support for pydantic-settings in Configuration.from_pydantic() method

This commit is contained in:
Aleksandr Tsariapkin 2023-11-26 14:18:05 +04:00
parent cc2304e46e
commit 18143a4e44
8 changed files with 8483 additions and 8304 deletions

View File

@ -12,6 +12,7 @@ pyyaml
httpx
fastapi
pydantic
pydantic-settings
numpy
scipy
boto3

View File

@ -1,2 +1,3 @@
flask
flask~=2.2.2
Werkzeug~=2.2.2
aiohttp

File diff suppressed because it is too large Load Diff

View File

@ -27,9 +27,12 @@ except ImportError:
yaml = None
try:
import pydantic
import pydantic_settings
except ImportError:
pydantic = None
try:
import pydantic as pydantic_settings
except ImportError:
pydantic_settings = None
from . import resources
@ -544,7 +547,7 @@ if yaml:
else:
class YamlLoader: ...
if pydantic:
PydanticSettings = pydantic.BaseSettings
if pydantic_settings:
PydanticSettings = pydantic_settings.BaseSettings
else:
PydanticSettings = Any

View File

@ -49,9 +49,12 @@ except ImportError:
yaml = None
try:
import pydantic
import pydantic_settings
except ImportError:
pydantic = None
try:
import pydantic as pydantic_settings
except ImportError:
pydantic_settings = None
from .errors import (
Error,
@ -1786,36 +1789,38 @@ cdef class ConfigurationOption(Provider):
Loaded configuration is merged recursively over existing configuration.
:param settings: Pydantic settings instances.
:type settings: :py:class:`pydantic.BaseSettings`
:type settings: :py:class:`pydantic.BaseSettings` or :py:class:`pydantic_settings.BaseSettings`
:param required: When required is True, raise an exception if settings dict is empty.
:type required: bool
:param kwargs: Keyword arguments forwarded to ``pydantic.BaseSettings.dict()`` call.
:param kwargs: Keyword arguments forwarded to ``pydantic.BaseSettings.dict()``
or ``pydantic_settings.BaseSettings.model_dump()`` call, depending on the used package.
:type kwargs: Dict[Any, Any]
:rtype: None
"""
if pydantic is None:
if pydantic_settings is None:
raise Error(
"Unable to load pydantic configuration - pydantic is not installed. "
"Install pydantic or install Dependency Injector with pydantic extras: "
"Unable to load pydantic configuration - neither pydantic_settings nor pydantic v1 is not installed. "
"Install pydantic_settings or pydantic v1 or install Dependency Injector with pydantic extras: "
"\"pip install dependency-injector[pydantic]\""
)
if isinstance(settings, CLASS_TYPES) and issubclass(settings, pydantic.BaseSettings):
if isinstance(settings, CLASS_TYPES) and issubclass(settings, pydantic_settings.BaseSettings):
raise Error(
"Got settings class, but expect instance: "
"instead \"{0}\" use \"{0}()\"".format(settings.__name__)
)
if not isinstance(settings, pydantic.BaseSettings):
if not isinstance(settings, pydantic_settings.BaseSettings):
raise Error(
"Unable to recognize settings instance, expect \"pydantic.BaseSettings\", "
"got {0} instead".format(settings)
)
self.from_dict(settings.dict(**kwargs), required=required)
settings_dict = settings.model_dump(**kwargs) if hasattr(settings, "model_dump") else settings.dict(**kwargs)
self.from_dict(settings_dict, required=required)
def from_dict(self, options, required=UNDEFINED):
"""Load configuration from the dictionary.
@ -2355,36 +2360,38 @@ cdef class Configuration(Object):
Loaded configuration is merged recursively over existing configuration.
:param settings: Pydantic settings instances.
:type settings: :py:class:`pydantic.BaseSettings`
:type settings: :py:class:`pydantic.BaseSettings` or :py:class:`pydantic_settings.BaseSettings`
:param required: When required is True, raise an exception if settings dict is empty.
:type required: bool
:param kwargs: Keyword arguments forwarded to ``pydantic.BaseSettings.dict()`` call.
:param kwargs: Keyword arguments forwarded to ``pydantic.BaseSettings.dict()``
or ``pydantic.BaseSettings.model_dump call()`` call, depending on the used package.
:type kwargs: Dict[Any, Any]
:rtype: None
"""
if pydantic is None:
if pydantic_settings is None:
raise Error(
"Unable to load pydantic configuration - pydantic is not installed. "
"Install pydantic or install Dependency Injector with pydantic extras: "
"Unable to load pydantic configuration - neither pydantic_settings nor pydantic v1 is not installed. "
"Install pydantic_settings or pydantic v1 or install Dependency Injector with pydantic extras: "
"\"pip install dependency-injector[pydantic]\""
)
if isinstance(settings, CLASS_TYPES) and issubclass(settings, pydantic.BaseSettings):
if isinstance(settings, CLASS_TYPES) and issubclass(settings, pydantic_settings.BaseSettings):
raise Error(
"Got settings class, but expect instance: "
"instead \"{0}\" use \"{0}()\"".format(settings.__name__)
)
if not isinstance(settings, pydantic.BaseSettings):
if not isinstance(settings, pydantic_settings.BaseSettings):
raise Error(
"Unable to recognize settings instance, expect \"pydantic.BaseSettings\", "
"got {0} instead".format(settings)
)
self.from_dict(settings.dict(**kwargs), required=required)
settings_dict = settings.model_dump(**kwargs) if hasattr(settings, "model_dump") else settings.dict(**kwargs)
self.from_dict(settings_dict, required=required)
def from_dict(self, options, required=UNDEFINED):
"""Load configuration from the dictionary.

View File

@ -1,7 +1,11 @@
from pathlib import Path
from dependency_injector import providers
from pydantic import BaseSettings as PydanticSettings
try:
from pydantic_settings import BaseSettings as PydanticSettings
except ImportError:
from pydantic import BaseSettings as PydanticSettings
# Test 1: to check the getattr

View File

@ -4,38 +4,44 @@ import pydantic
from dependency_injector import providers, errors
from pytest import fixture, mark, raises
try:
from pydantic_settings import BaseSettings
except ImportError:
BaseSettings = pydantic.BaseSettings
class Section11(pydantic.BaseModel):
value1 = 1
value1: int = 1
class Section12(pydantic.BaseModel):
value2 = 2
value2: int = 2
class Settings1(pydantic.BaseSettings):
section1 = Section11()
section2 = Section12()
class Settings1(BaseSettings):
section1: Section11 = Section11()
section2: Section12 = Section12()
class Section21(pydantic.BaseModel):
value1 = 11
value11 = 11
value1: int = 11
value11: int = 11
class Section3(pydantic.BaseModel):
value3 = 3
value3: int = 3
class Settings2(pydantic.BaseSettings):
section1 = Section21()
section3 = Section3()
class Settings2(BaseSettings):
section1: Section21 = Section21()
section3: Section3 = Section3()
@fixture
def no_pydantic_module_installed():
providers.pydantic = None
pydantic_setting = providers.pydantic_settings
providers.pydantic_settings = None
yield
providers.pydantic = pydantic
providers.pydantic_settings = pydantic_setting
def test(config):
@ -82,46 +88,46 @@ def test_merge(config):
def test_empty_settings(config):
config.from_pydantic(pydantic.BaseSettings())
config.from_pydantic(BaseSettings())
assert config() == {}
@mark.parametrize("config_type", ["strict"])
def test_empty_settings_strict_mode(config):
with raises(ValueError):
config.from_pydantic(pydantic.BaseSettings())
config.from_pydantic(BaseSettings())
def test_option_empty_settings(config):
config.option.from_pydantic(pydantic.BaseSettings())
config.option.from_pydantic(BaseSettings())
assert config.option() == {}
@mark.parametrize("config_type", ["strict"])
def test_option_empty_settings_strict_mode(config):
with raises(ValueError):
config.option.from_pydantic(pydantic.BaseSettings())
config.option.from_pydantic(BaseSettings())
def test_required_empty_settings(config):
with raises(ValueError):
config.from_pydantic(pydantic.BaseSettings(), required=True)
config.from_pydantic(BaseSettings(), required=True)
def test_required_option_empty_settings(config):
with raises(ValueError):
config.option.from_pydantic(pydantic.BaseSettings(), required=True)
config.option.from_pydantic(BaseSettings(), required=True)
@mark.parametrize("config_type", ["strict"])
def test_not_required_empty_settings_strict_mode(config):
config.from_pydantic(pydantic.BaseSettings(), required=False)
config.from_pydantic(BaseSettings(), required=False)
assert config() == {}
@mark.parametrize("config_type", ["strict"])
def test_not_required_option_empty_settings_strict_mode(config):
config.option.from_pydantic(pydantic.BaseSettings(), required=False)
config.option.from_pydantic(BaseSettings(), required=False)
assert config.option() == {}
assert config() == {"option": {}}
@ -167,8 +173,8 @@ def test_no_pydantic_installed(config):
with raises(errors.Error) as error:
config.from_pydantic(Settings1())
assert error.value.args[0] == (
"Unable to load pydantic configuration - pydantic is not installed. "
"Install pydantic or install Dependency Injector with pydantic extras: "
"Unable to load pydantic configuration - neither pydantic_settings nor pydantic v1 is not installed. "
"Install pydantic_settings or pydantic v1 or install Dependency Injector with pydantic extras: "
"\"pip install dependency-injector[pydantic]\""
)
@ -178,7 +184,7 @@ def test_option_no_pydantic_installed(config):
with raises(errors.Error) as error:
config.option.from_pydantic(Settings1())
assert error.value.args[0] == (
"Unable to load pydantic configuration - pydantic is not installed. "
"Install pydantic or install Dependency Injector with pydantic extras: "
"Unable to load pydantic configuration - neither pydantic_settings nor pydantic v1 is not installed. "
"Install pydantic_settings or pydantic v1 or install Dependency Injector with pydantic extras: "
"\"pip install dependency-injector[pydantic]\""
)

View File

@ -5,31 +5,37 @@ from dependency_injector import providers
from pytest import fixture, mark, raises
try:
from pydantic_settings import BaseSettings
except ImportError:
BaseSettings = pydantic.BaseSettings
class Section11(pydantic.BaseModel):
value1 = 1
value1: int = 1
class Section12(pydantic.BaseModel):
value2 = 2
value2: int = 2
class Settings1(pydantic.BaseSettings):
section1 = Section11()
section2 = Section12()
class Settings1(BaseSettings):
section1: Section11 = Section11()
section2: Section12 = Section12()
class Section21(pydantic.BaseModel):
value1 = 11
value11 = 11
value1: int = 11
value11: int = 11
class Section3(pydantic.BaseModel):
value3 = 3
value3: int = 3
class Settings2(pydantic.BaseSettings):
section1 = Section21()
section3 = Section3()
class Settings2(BaseSettings):
section1: Section21 = Section21()
section3: Section3 = Section3()
@fixture
@ -86,10 +92,10 @@ def test_copy(config, pydantic_settings_1, pydantic_settings_2):
def test_set_pydantic_settings(config):
class Settings3(pydantic.BaseSettings):
class Settings3(BaseSettings):
...
class Settings4(pydantic.BaseSettings):
class Settings4(BaseSettings):
...
settings_3 = Settings3()
@ -100,27 +106,27 @@ def test_set_pydantic_settings(config):
def test_file_does_not_exist(config):
config.set_pydantic_settings([pydantic.BaseSettings()])
config.set_pydantic_settings([BaseSettings()])
config.load()
assert config() == {}
@mark.parametrize("config_type", ["strict"])
def test_file_does_not_exist_strict_mode(config):
config.set_pydantic_settings([pydantic.BaseSettings()])
config.set_pydantic_settings([BaseSettings()])
with raises(ValueError):
config.load()
assert config() == {}
def test_required_file_does_not_exist(config):
config.set_pydantic_settings([pydantic.BaseSettings()])
config.set_pydantic_settings([BaseSettings()])
with raises(ValueError):
config.load(required=True)
@mark.parametrize("config_type", ["strict"])
def test_not_required_file_does_not_exist_strict_mode(config):
config.set_pydantic_settings([pydantic.BaseSettings()])
config.set_pydantic_settings([BaseSettings()])
config.load(required=False)
assert config() == {}