feat(shared): pass config data via init (#2042)
This commit is contained in:
parent
2bde574487
commit
1147853c63
|
@ -34,7 +34,7 @@ def cli(
|
||||||
Run analyzer.
|
Run analyzer.
|
||||||
"""
|
"""
|
||||||
setup_logger(level_from_name(log_level), log_filepath)
|
setup_logger(level_from_name(log_level), log_filepath)
|
||||||
config = Config(filepath=config_filepath)
|
config = Config(config_filepath)
|
||||||
|
|
||||||
# Start up the StatusReporter process
|
# Start up the StatusReporter process
|
||||||
StatusReporter.start_thread(retry_queue_filepath)
|
StatusReporter.start_thread(retry_queue_filepath)
|
||||||
|
|
|
@ -75,7 +75,7 @@ class ApiClient:
|
||||||
def __init__(self, logger=None, config_path="/etc/libretime/config.yml"):
|
def __init__(self, logger=None, config_path="/etc/libretime/config.yml"):
|
||||||
self.logger = logger or logging
|
self.logger = logger or logging
|
||||||
|
|
||||||
config = Config(filepath=config_path)
|
config = Config(config_path)
|
||||||
self.base_url = config.general.public_url
|
self.base_url = config.general.public_url
|
||||||
self.api_key = config.general.api_key
|
self.api_key = config.general.api_key
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ from ._schema import Config
|
||||||
LIBRETIME_LOG_FILEPATH = getenv("LIBRETIME_LOG_FILEPATH")
|
LIBRETIME_LOG_FILEPATH = getenv("LIBRETIME_LOG_FILEPATH")
|
||||||
LIBRETIME_CONFIG_FILEPATH = getenv("LIBRETIME_CONFIG_FILEPATH")
|
LIBRETIME_CONFIG_FILEPATH = getenv("LIBRETIME_CONFIG_FILEPATH")
|
||||||
|
|
||||||
CONFIG = Config(filepath=LIBRETIME_CONFIG_FILEPATH)
|
CONFIG = Config(LIBRETIME_CONFIG_FILEPATH)
|
||||||
|
|
||||||
SECRET_KEY = CONFIG.general.api_key
|
SECRET_KEY = CONFIG.general.api_key
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ def cli(log_level: str, log_filepath: Optional[Path], config_filepath: Optional[
|
||||||
Run playout.
|
Run playout.
|
||||||
"""
|
"""
|
||||||
setup_logger(level_from_name(log_level), log_filepath)
|
setup_logger(level_from_name(log_level), log_filepath)
|
||||||
config = Config(filepath=config_filepath)
|
config = Config(config_filepath)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for dir_path in [CACHE_DIR, RECORD_DIR]:
|
for dir_path in [CACHE_DIR, RECORD_DIR]:
|
||||||
|
|
|
@ -29,7 +29,7 @@ class Config(BaseConfig):
|
||||||
rabbitmq: RabbitMQConfig
|
rabbitmq: RabbitMQConfig
|
||||||
analyzer: AnalyzerConfig
|
analyzer: AnalyzerConfig
|
||||||
|
|
||||||
config = Config(filepath="/etc/libretime/config.yml")
|
config = Config("/etc/libretime/config.yml")
|
||||||
```
|
```
|
||||||
|
|
||||||
> Don't instantiate a sub model if it has a required field, otherwise the `Config` class import will raise a `ValidationError`.
|
> Don't instantiate a sub model if it has a required field, otherwise the `Config` class import will raise a `ValidationError`.
|
||||||
|
|
|
@ -24,20 +24,23 @@ class BaseConfig(BaseModel):
|
||||||
:returns: configuration class
|
:returns: configuration class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=no-self-argument
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
_self,
|
||||||
|
_filepath: Optional[Union[Path, str]] = None,
|
||||||
*,
|
*,
|
||||||
env_prefix: str = DEFAULT_ENV_PREFIX,
|
_env_prefix: str = DEFAULT_ENV_PREFIX,
|
||||||
env_delimiter: str = "_",
|
_env_delimiter: str = "_",
|
||||||
filepath: Optional[Union[Path, str]] = None,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
if filepath is not None:
|
if _filepath is not None:
|
||||||
filepath = Path(filepath)
|
_filepath = Path(_filepath)
|
||||||
|
|
||||||
env_loader = EnvLoader(self.schema(), env_prefix, env_delimiter)
|
env_loader = EnvLoader(_self.schema(), _env_prefix, _env_delimiter)
|
||||||
|
|
||||||
values = deep_merge_dict(
|
values = deep_merge_dict(
|
||||||
self._load_file_values(filepath),
|
kwargs,
|
||||||
|
_self._load_file_values(_filepath),
|
||||||
env_loader.load(),
|
env_loader.load(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -67,36 +70,39 @@ class BaseConfig(BaseModel):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def deep_merge_dict(base: Dict[str, Any], next_: Dict[str, Any]) -> Dict[str, Any]:
|
def deep_merge_dict(base: Dict[str, Any], *elements: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
result = base.copy()
|
result = base.copy()
|
||||||
for key, value in next_.items():
|
|
||||||
if key in result:
|
|
||||||
if isinstance(result[key], dict) and isinstance(value, dict):
|
|
||||||
result[key] = deep_merge_dict(result[key], value)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(result[key], list) and isinstance(value, list):
|
for element in elements:
|
||||||
result[key] = deep_merge_list(result[key], value)
|
for key, value in element.items():
|
||||||
continue
|
if key in result:
|
||||||
|
if isinstance(result[key], dict) and isinstance(value, dict):
|
||||||
|
result[key] = deep_merge_dict(result[key], value)
|
||||||
|
continue
|
||||||
|
|
||||||
if value:
|
if isinstance(result[key], list) and isinstance(value, list):
|
||||||
result[key] = value
|
result[key] = deep_merge_list(result[key], value)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if value:
|
||||||
|
result[key] = value
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def deep_merge_list(base: List[Any], next_: List[Any]) -> List[Any]:
|
def deep_merge_list(base: List[Any], *elements: List[Any]) -> List[Any]:
|
||||||
result: List[Any] = []
|
result: List[Any] = []
|
||||||
for base_item, next_item in zip_longest(base, next_):
|
for element in elements:
|
||||||
if isinstance(base_item, list) and isinstance(next_item, list):
|
for base_item, next_item in zip_longest(base, element):
|
||||||
result.append(deep_merge_list(base_item, next_item))
|
if isinstance(base_item, list) and isinstance(next_item, list):
|
||||||
continue
|
result.append(deep_merge_list(base_item, next_item))
|
||||||
|
continue
|
||||||
|
|
||||||
if isinstance(base_item, dict) and isinstance(next_item, dict):
|
if isinstance(base_item, dict) and isinstance(next_item, dict):
|
||||||
result.append(deep_merge_dict(base_item, next_item))
|
result.append(deep_merge_dict(base_item, next_item))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if next_item:
|
if next_item:
|
||||||
result.append(next_item)
|
result.append(next_item)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -57,7 +57,7 @@ def test_base_config(tmp_path: Path):
|
||||||
"WRONGPREFIX_API_KEY": "invalid",
|
"WRONGPREFIX_API_KEY": "invalid",
|
||||||
},
|
},
|
||||||
):
|
):
|
||||||
config = FixtureConfig(filepath=config_filepath)
|
config = FixtureConfig(config_filepath)
|
||||||
|
|
||||||
assert config.public_url == "http://libretime.example.com"
|
assert config.public_url == "http://libretime.example.com"
|
||||||
assert config.api_key == "f3bf04fc"
|
assert config.api_key == "f3bf04fc"
|
||||||
|
@ -69,7 +69,7 @@ def test_base_config(tmp_path: Path):
|
||||||
|
|
||||||
# Optional model: loading default values (rabbitmq)
|
# Optional model: loading default values (rabbitmq)
|
||||||
with mock.patch.dict(environ, {}):
|
with mock.patch.dict(environ, {}):
|
||||||
config = FixtureConfig(filepath=config_filepath)
|
config = FixtureConfig(config_filepath)
|
||||||
assert config.allowed_hosts == ["example.com", "sub.example.com"]
|
assert config.allowed_hosts == ["example.com", "sub.example.com"]
|
||||||
assert config.rabbitmq.host == "localhost"
|
assert config.rabbitmq.host == "localhost"
|
||||||
assert config.rabbitmq.port == 5672
|
assert config.rabbitmq.port == 5672
|
||||||
|
@ -82,7 +82,7 @@ def test_base_config(tmp_path: Path):
|
||||||
"LIBRETIME_ALLOWED_HOSTS": "example.com, changed.example.com",
|
"LIBRETIME_ALLOWED_HOSTS": "example.com, changed.example.com",
|
||||||
},
|
},
|
||||||
):
|
):
|
||||||
config = FixtureConfig(filepath=config_filepath)
|
config = FixtureConfig(config_filepath)
|
||||||
assert config.allowed_hosts == ["example.com", "changed.example.com"]
|
assert config.allowed_hosts == ["example.com", "changed.example.com"]
|
||||||
assert config.rabbitmq.host == "changed"
|
assert config.rabbitmq.host == "changed"
|
||||||
assert config.rabbitmq.port == 5672
|
assert config.rabbitmq.port == 5672
|
||||||
|
@ -111,19 +111,19 @@ def test_base_config_required_submodel(tmp_path: Path):
|
||||||
|
|
||||||
# With config file
|
# With config file
|
||||||
with mock.patch.dict(environ, {}):
|
with mock.patch.dict(environ, {}):
|
||||||
config = FixtureWithRequiredSubmodelConfig(filepath=config_filepath)
|
config = FixtureWithRequiredSubmodelConfig(config_filepath)
|
||||||
assert config.required.api_key == "test_key"
|
assert config.required.api_key == "test_key"
|
||||||
assert config.required.with_default == "original"
|
assert config.required.with_default == "original"
|
||||||
|
|
||||||
# With env variables
|
# With env variables
|
||||||
with mock.patch.dict(environ, {"LIBRETIME_REQUIRED_API_KEY": "test_key"}):
|
with mock.patch.dict(environ, {"LIBRETIME_REQUIRED_API_KEY": "test_key"}):
|
||||||
config = FixtureWithRequiredSubmodelConfig(filepath=None)
|
config = FixtureWithRequiredSubmodelConfig(None)
|
||||||
assert config.required.api_key == "test_key"
|
assert config.required.api_key == "test_key"
|
||||||
assert config.required.with_default == "original"
|
assert config.required.with_default == "original"
|
||||||
|
|
||||||
# With env variables override
|
# With env variables override
|
||||||
with mock.patch.dict(environ, {"LIBRETIME_REQUIRED_API_KEY": "changed"}):
|
with mock.patch.dict(environ, {"LIBRETIME_REQUIRED_API_KEY": "changed"}):
|
||||||
config = FixtureWithRequiredSubmodelConfig(filepath=config_filepath)
|
config = FixtureWithRequiredSubmodelConfig(config_filepath)
|
||||||
assert config.required.api_key == "changed"
|
assert config.required.api_key == "changed"
|
||||||
assert config.required.with_default == "original"
|
assert config.required.with_default == "original"
|
||||||
|
|
||||||
|
@ -135,14 +135,29 @@ def test_base_config_required_submodel(tmp_path: Path):
|
||||||
"LIBRETIME_REQUIRED_WITH_DEFAULT": "changed",
|
"LIBRETIME_REQUIRED_WITH_DEFAULT": "changed",
|
||||||
},
|
},
|
||||||
):
|
):
|
||||||
config = FixtureWithRequiredSubmodelConfig(filepath=config_filepath)
|
config = FixtureWithRequiredSubmodelConfig(config_filepath)
|
||||||
assert config.required.api_key == "changed"
|
assert config.required.api_key == "changed"
|
||||||
assert config.required.with_default == "changed"
|
assert config.required.with_default == "changed"
|
||||||
|
|
||||||
# Raise validation error
|
# Raise validation error
|
||||||
with mock.patch.dict(environ, {}):
|
with mock.patch.dict(environ, {}):
|
||||||
with raises(SystemExit):
|
with raises(SystemExit):
|
||||||
FixtureWithRequiredSubmodelConfig(filepath=None)
|
FixtureWithRequiredSubmodelConfig(None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_base_config_from_init() -> None:
|
||||||
|
class FromInitFixtureConfig(BaseConfig):
|
||||||
|
found: str
|
||||||
|
override: str
|
||||||
|
|
||||||
|
with mock.patch.dict(environ, {"LIBRETIME_OVERRIDE": "changed"}):
|
||||||
|
config = FromInitFixtureConfig(
|
||||||
|
found="changed",
|
||||||
|
override="invalid",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert config.found == "changed"
|
||||||
|
assert config.override == "changed"
|
||||||
|
|
||||||
|
|
||||||
FIXTURE_CONFIG_RAW_MISSING = """
|
FIXTURE_CONFIG_RAW_MISSING = """
|
||||||
|
@ -169,4 +184,4 @@ def test_load_config_error(tmp_path: Path, raw, exception):
|
||||||
|
|
||||||
with raises(exception):
|
with raises(exception):
|
||||||
with mock.patch.dict(environ, {}):
|
with mock.patch.dict(environ, {}):
|
||||||
FixtureConfig(filepath=config_filepath)
|
FixtureConfig(config_filepath)
|
||||||
|
|
|
@ -11,7 +11,7 @@ class Config(BaseConfig):
|
||||||
|
|
||||||
LIBRETIME_CONFIG_FILEPATH = getenv("LIBRETIME_CONFIG_FILEPATH")
|
LIBRETIME_CONFIG_FILEPATH = getenv("LIBRETIME_CONFIG_FILEPATH")
|
||||||
|
|
||||||
config = Config(filepath=LIBRETIME_CONFIG_FILEPATH)
|
config = Config(LIBRETIME_CONFIG_FILEPATH)
|
||||||
|
|
||||||
# Celery amqp settings
|
# Celery amqp settings
|
||||||
BROKER_URL = config.rabbitmq.url
|
BROKER_URL = config.rabbitmq.url
|
||||||
|
|
Loading…
Reference in New Issue