feat(shared): load env config using jsonschema
The env loader is now capable of loading lists of objects, union types or list of union types from the env variables. They are some limitations: for example it doesn't support unions of different shapes `list | dict` or `str | dict`.
This commit is contained in:
parent
6c449e3019
commit
bcd877266f
3 changed files with 500 additions and 47 deletions
189
shared/tests/config/env_test.py
Normal file
189
shared/tests/config/env_test.py
Normal file
|
@ -0,0 +1,189 @@
|
|||
# pylint: disable=protected-access
|
||||
from os import environ
|
||||
from typing import List, Union
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
|
||||
from libretime_shared.config import BaseConfig
|
||||
from libretime_shared.config._env import EnvLoader
|
||||
|
||||
ENV_SCHEMA_OBJ_WITH_STR = {
|
||||
"type": "object",
|
||||
"properties": {"a_str": {"type": "string"}},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"env_parent, env, schema, expected",
|
||||
[
|
||||
(
|
||||
"PRE",
|
||||
{"PRE_A_STR": "found"},
|
||||
{"a_str": {"type": "string"}},
|
||||
{"a_str": "found"},
|
||||
),
|
||||
(
|
||||
"PRE",
|
||||
{"PRE_OBJ_A_STR": "found"},
|
||||
{"obj": ENV_SCHEMA_OBJ_WITH_STR},
|
||||
{"obj": {"a_str": "found"}},
|
||||
),
|
||||
(
|
||||
"PRE",
|
||||
{"PRE_ARR1": "one, two"},
|
||||
{"arr1": {"type": "array", "items": {"type": "string"}}},
|
||||
{"arr1": ["one", "two"]},
|
||||
),
|
||||
(
|
||||
"PRE",
|
||||
{
|
||||
"PRE_ARR2_0_A_STR": "one",
|
||||
"PRE_ARR2_1_A_STR": "two",
|
||||
"PRE_ARR2_3_A_STR": "ten",
|
||||
},
|
||||
{"arr2": {"type": "array", "items": ENV_SCHEMA_OBJ_WITH_STR}},
|
||||
{
|
||||
"arr2": [
|
||||
{"a_str": "one"},
|
||||
{"a_str": "two"},
|
||||
None,
|
||||
{"a_str": "ten"},
|
||||
]
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_env_config_loader_get_object(
|
||||
env_parent,
|
||||
env,
|
||||
schema,
|
||||
expected,
|
||||
):
|
||||
with mock.patch.dict(environ, env):
|
||||
loader = EnvLoader(schema={}, env_prefix="PRE")
|
||||
result = loader._get_object(env_parent, {"properties": schema})
|
||||
assert result == expected
|
||||
|
||||
|
||||
class FirstChildConfig(BaseModel):
|
||||
a_child_str: str
|
||||
|
||||
|
||||
class SecondChildConfig(BaseModel):
|
||||
a_child_str: str
|
||||
a_child_int: int
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class FixtureConfig(BaseConfig):
|
||||
a_str: str
|
||||
a_list_of_str: List[str]
|
||||
a_obj: FirstChildConfig
|
||||
a_obj_with_default: FirstChildConfig = FirstChildConfig(a_child_str="default")
|
||||
a_list_of_obj: List[FirstChildConfig]
|
||||
a_union_str_or_int: Union[str, int]
|
||||
a_union_obj: Union[FirstChildConfig, SecondChildConfig]
|
||||
a_list_of_union_str_or_int: List[Union[str, int]]
|
||||
a_list_of_union_obj: List[Union[FirstChildConfig, SecondChildConfig]]
|
||||
|
||||
|
||||
ENV_SCHEMA = FixtureConfig.schema()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"env_name, env, schema, expected",
|
||||
[
|
||||
(
|
||||
"PRE_A_STR",
|
||||
{"PRE_A_STR": "found"},
|
||||
ENV_SCHEMA["properties"]["a_str"],
|
||||
"found",
|
||||
),
|
||||
(
|
||||
"PRE_A_LIST_OF_STR",
|
||||
{"PRE_A_LIST_OF_STR": "one, two"},
|
||||
ENV_SCHEMA["properties"]["a_list_of_str"],
|
||||
["one", "two"],
|
||||
),
|
||||
(
|
||||
"PRE_A_OBJ",
|
||||
{"PRE_A_OBJ_A_CHILD_STR": "found"},
|
||||
ENV_SCHEMA["properties"]["a_obj"],
|
||||
{"a_child_str": "found"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_env_config_loader_get(
|
||||
env_name,
|
||||
env,
|
||||
schema,
|
||||
expected,
|
||||
):
|
||||
with mock.patch.dict(environ, env):
|
||||
loader = EnvLoader(schema=ENV_SCHEMA, env_prefix="PRE")
|
||||
result = loader._get(env_name, schema)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_env_config_loader_load_empty():
|
||||
with mock.patch.dict(environ, {}):
|
||||
loader = EnvLoader(schema=ENV_SCHEMA, env_prefix="PRE")
|
||||
result = loader.load()
|
||||
assert not result
|
||||
|
||||
|
||||
def test_env_config_loader_load():
|
||||
with mock.patch.dict(
|
||||
environ,
|
||||
{
|
||||
"PRE_A_STR": "found",
|
||||
"PRE_A_LIST_OF_STR": "one, two",
|
||||
"PRE_A_OBJ": "invalid",
|
||||
"PRE_A_OBJ_A_CHILD_STR": "found",
|
||||
"PRE_A_OBJ_WITH_DEFAULT_A_CHILD_STR": "found",
|
||||
"PRE_A_LIST_OF_OBJ": "invalid",
|
||||
"PRE_A_LIST_OF_OBJ_0_A_CHILD_STR": "found",
|
||||
"PRE_A_LIST_OF_OBJ_1_A_CHILD_STR": "found",
|
||||
"PRE_A_LIST_OF_OBJ_3_A_CHILD_STR": "found",
|
||||
"PRE_A_LIST_OF_OBJ_INVALID": "invalid",
|
||||
"PRE_A_UNION_STR_OR_INT": "found",
|
||||
"PRE_A_UNION_OBJ_A_CHILD_STR": "found",
|
||||
"PRE_A_UNION_OBJ_A_CHILD_INT": "found",
|
||||
"PRE_A_LIST_OF_UNION_STR_OR_INT": "one, two, 3",
|
||||
"PRE_A_LIST_OF_UNION_STR_OR_INT_3": "4",
|
||||
"PRE_A_LIST_OF_UNION_OBJ": "invalid",
|
||||
"PRE_A_LIST_OF_UNION_OBJ_0_A_CHILD_STR": "found",
|
||||
"PRE_A_LIST_OF_UNION_OBJ_1_A_CHILD_STR": "found",
|
||||
"PRE_A_LIST_OF_UNION_OBJ_1_A_CHILD_INT": "found",
|
||||
"PRE_A_LIST_OF_UNION_OBJ_3_A_CHILD_INT": "found",
|
||||
"PRE_A_LIST_OF_UNION_OBJ_INVALID": "invalid",
|
||||
},
|
||||
):
|
||||
loader = EnvLoader(schema=ENV_SCHEMA, env_prefix="PRE")
|
||||
result = loader.load()
|
||||
assert result == {
|
||||
"a_str": "found",
|
||||
"a_list_of_str": ["one", "two"],
|
||||
"a_obj": {"a_child_str": "found"},
|
||||
"a_obj_with_default": {"a_child_str": "found"},
|
||||
"a_list_of_obj": [
|
||||
{"a_child_str": "found"},
|
||||
{"a_child_str": "found"},
|
||||
None,
|
||||
{"a_child_str": "found"},
|
||||
],
|
||||
"a_union_str_or_int": "found",
|
||||
"a_union_obj": {
|
||||
"a_child_str": "found",
|
||||
"a_child_int": "found",
|
||||
},
|
||||
"a_list_of_union_str_or_int": ["one", "two", "3", "4"],
|
||||
"a_list_of_union_obj": [
|
||||
{"a_child_str": "found"},
|
||||
{"a_child_str": "found", "a_child_int": "found"},
|
||||
None,
|
||||
{"a_child_int": "found"},
|
||||
],
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue