Skip to content

Commit

Permalink
Integrate YamlConfigSettingsSource into BaseConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
tdg5 committed Jan 14, 2024
1 parent 831ff64 commit 0a4bf7e
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 13 deletions.
39 changes: 37 additions & 2 deletions service_oriented/application/config/base_config.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
from typing import Any, Optional
from typing import Any, Optional, Tuple, Type

from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic_settings import (
BaseSettings,
PydanticBaseSettingsSource,
SettingsConfigDict,
)

from service_oriented.application.config.yaml_config_settings_source import (
YamlConfigSettingsSource,
)
from service_oriented.deployment_environment import DeploymentEnvironment


class BaseConfig(BaseSettings):
model_config = SettingsConfigDict()

deployment_environment: DeploymentEnvironment
yaml_config_path: Optional[str] = None

def __init__(self, *args: Any, **kwargs: Any):
if not self.model_config.get("env_nested_delimiter"):
Expand All @@ -18,3 +26,30 @@ def __init__(self, *args: Any, **kwargs: Any):
raise RuntimeError("env_prefix model config is required")

super().__init__(*args, **kwargs)

@classmethod
def settings_customise_sources(
cls,
settings_cls: Type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> Tuple[PydanticBaseSettingsSource, ...]:
yaml_config_settings = YamlConfigSettingsSource.from_sources(
settings_cls=settings_cls,
sources=[
init_settings,
env_settings,
file_secret_settings,
dotenv_settings,
],
)

return (
init_settings,
env_settings,
file_secret_settings,
yaml_config_settings,
dotenv_settings,
)
93 changes: 82 additions & 11 deletions service_oriented_test/application/config/base_config_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import os
from tempfile import NamedTemporaryFile, TemporaryDirectory

import pytest
from pydantic import ValidationError
from pydantic_settings import SettingsConfigDict
Expand All @@ -14,21 +17,10 @@
)


class ConfigWithoutEnvPrefix(BaseConfig):
model_config = SettingsConfigDict(env_nested_delimiter="__")


class ConfigWithoutEnvNestedDelimiter(BaseConfig):
model_config = SettingsConfigDict(env_prefix="prefix_")


class ConfigWithAllTheThings(BaseConfig):
model_config = SettingsConfigDict(
env_nested_delimiter="__",
env_prefix="test_",
)


def test_env_nested_delimiter_is_required() -> None:
with pytest.raises(RuntimeError) as exinfo:
ConfigWithoutEnvNestedDelimiter(
Expand All @@ -39,6 +31,10 @@ def test_env_nested_delimiter_is_required() -> None:
assert "env_nested_delimiter model config is required" == exception_message


class ConfigWithoutEnvPrefix(BaseConfig):
model_config = SettingsConfigDict(env_nested_delimiter="__")


def test_env_prefix_is_required() -> None:
with pytest.raises(RuntimeError) as exinfo:
ConfigWithoutEnvPrefix(deployment_environment=TEST_DEPLOYMENT_ENVIRONMENT)
Expand All @@ -47,6 +43,13 @@ def test_env_prefix_is_required() -> None:
assert "env_prefix model config is required" == exception_message


class ConfigWithAllTheThings(BaseConfig):
model_config = SettingsConfigDict(
env_nested_delimiter="__",
env_prefix="base_config_test_",
)


def test_deployment_environment_is_accessible() -> None:
config = ConfigWithAllTheThings(
deployment_environment=TEST_DEPLOYMENT_ENVIRONMENT,
Expand All @@ -63,3 +66,71 @@ def test_deployment_environment_is_required() -> None:
assert "1 validation error for ConfigWithAllTheThings" in exception_message
assert "deployment_environment" in exception_message
assert "Field required" in exception_message


def test_yaml_config_path_is_accessible() -> None:
yaml_config_path = "foo"
config = ConfigWithAllTheThings(
deployment_environment=TEST_DEPLOYMENT_ENVIRONMENT,
yaml_config_path=yaml_config_path,
)
assert yaml_config_path == config.yaml_config_path


def test_yaml_config_path_is_optional() -> None:
config = ConfigWithAllTheThings(
deployment_environment=TEST_DEPLOYMENT_ENVIRONMENT,
)
assert config.yaml_config_path is None


class ConfigForTestingSourcePriorities(ConfigWithAllTheThings):
layer_one: str
layer_two: str
layer_three: str
layer_four: str
layer_five: str


def test_setting_source_priorities() -> None:
os.environ["BASE_CONFIG_TEST_LAYER_ONE"] = "env"
os.environ["BASE_CONFIG_TEST_LAYER_TWO"] = "env"
with TemporaryDirectory() as secrets_dir:
for config_name in ["layer_one", "layer_two", "layer_three"]:
with open(
f"{secrets_dir}/base_config_test_{config_name}", "w+"
) as secret_file:
secret_file.write("secret")
with NamedTemporaryFile(mode="w+") as yaml_file:
yaml_file.write(
"""
layer_one: yaml
layer_two: yaml
layer_three: yaml
layer_four: yaml
"""
)
yaml_file.flush()
with NamedTemporaryFile(mode="w+") as dotenv_file:
dotenv_file.write(
"""
BASE_CONFIG_TEST_LAYER_ONE=dotenv
BASE_CONFIG_TEST_LAYER_TWO=dotenv
BASE_CONFIG_TEST_LAYER_THREE=dotenv
BASE_CONFIG_TEST_LAYER_FOUR=dotenv
BASE_CONFIG_TEST_LAYER_FIVE=dotenv
"""
)
dotenv_file.flush()
config = ConfigForTestingSourcePriorities(
_env_file=dotenv_file.name,
_secrets_dir=secrets_dir,
layer_one="init",
deployment_environment=TEST_DEPLOYMENT_ENVIRONMENT,
yaml_config_path=yaml_file.name,
)
assert "init" == config.layer_one
assert "env" == config.layer_two
assert "secret" == config.layer_three
assert "yaml" == config.layer_four
assert "dotenv" == config.layer_five

0 comments on commit 0a4bf7e

Please sign in to comment.