959a391334
Some checks failed
publish docs / publish-docs (push) Has been cancelled
release-please / release-please (push) Has been cancelled
tests / setup (push) Has been cancelled
tests / ${{ matrix.quality-command }} (black) (push) Has been cancelled
tests / ${{ matrix.quality-command }} (mypy) (push) Has been cancelled
tests / ${{ matrix.quality-command }} (ruff) (push) Has been cancelled
tests / test (push) Has been cancelled
tests / all_checks_passed (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
42 lines
1.3 KiB
Python
42 lines
1.3 KiB
Python
import os
|
|
import re
|
|
import typing
|
|
from typing import Any, TextIO
|
|
|
|
from yaml import SafeLoader
|
|
|
|
_env_replace_matcher = re.compile(r"\$\{(\w|_)+:?.*}")
|
|
|
|
|
|
@typing.no_type_check # pyaml does not have good hints, everything is Any
|
|
def load_yaml_with_envvars(
|
|
stream: TextIO, environ: dict[str, Any] = os.environ
|
|
) -> dict[str, Any]:
|
|
"""Load yaml file with environment variable expansion.
|
|
|
|
The pattern ${VAR} or ${VAR:default} will be replaced with
|
|
the value of the environment variable.
|
|
"""
|
|
loader = SafeLoader(stream)
|
|
|
|
def load_env_var(_, node) -> str:
|
|
"""Extract the matched value, expand env variable, and replace the match."""
|
|
value = str(node.value).removeprefix("${").removesuffix("}")
|
|
split = value.split(":", 1)
|
|
env_var = split[0]
|
|
value = environ.get(env_var)
|
|
default = None if len(split) == 1 else split[1]
|
|
if value is None and default is None:
|
|
raise ValueError(
|
|
f"Environment variable {env_var} is not set and not default was provided"
|
|
)
|
|
return value or default
|
|
|
|
loader.add_implicit_resolver("env_var_replacer", _env_replace_matcher, None)
|
|
loader.add_constructor("env_var_replacer", load_env_var)
|
|
|
|
try:
|
|
return loader.get_single_data()
|
|
finally:
|
|
loader.dispose()
|