import os import re from functools import partial, reduce from .base import * import yaml has_regex_module = False ENV_VAR_MATCHER = re.compile( r""" \$\{ # match characters `${` literally ([^}:\s]+) # 1st group: matches any character except `}` or `:` :? # matches the literal `:` character zero or one times ([^}]+)? # 2nd group: matches any character except `}` \} # match character `}` literally """, re.VERBOSE ) IMPLICIT_ENV_VAR_MATCHER = re.compile( r""" .* # matches any number of any characters \$\{.*\} # matches any number of any characters # between `${` and `}` literally .* # matches any number of any characters """, re.VERBOSE ) RECURSIVE_ENV_VAR_MATCHER = re.compile( r""" \$\{ # match characters `${` literally ([^}]+)? # matches any character except `}` \} # match character `}` literally ([^$}]+)? # matches any character except `}` or `$` \} # match character `}` literally """, re.VERBOSE, ) def _replace_env_var(match): env_var, default = match.groups() value = os.environ.get(env_var, None) if value is None: # expand default using other vars if default is None: # regex module return None instead of # '' if engine didn't entered default capture group default = '' value = default while IMPLICIT_ENV_VAR_MATCHER.match(value): # pragma: no cover value = ENV_VAR_MATCHER.sub(_replace_env_var, value) return value def env_var_constructor(loader, node, raw=False): raw_value = loader.construct_scalar(node) # detect and error on recursive environment variables if not has_regex_module and RECURSIVE_ENV_VAR_MATCHER.match( raw_value ): # pragma: no cover raise Exception( "Nested environment variable lookup requires the `regex` module" ) value = ENV_VAR_MATCHER.sub(_replace_env_var, raw_value) if value == raw_value: return value # avoid recursion return value if raw else yaml.safe_load(value) def build_config(config_name=None): CONFIG_NAME = config_name or 'config.yml' path = f'{get_project_path()}{os.path.sep}{CONFIG_NAME}' with open(path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) yaml.add_constructor('!env_var', env_var_constructor, yaml.SafeLoader) yaml.add_constructor( '!raw_env_var', partial(env_var_constructor, raw=True), yaml.SafeLoader ) yaml.add_implicit_resolver( '!env_var', IMPLICIT_ENV_VAR_MATCHER, Loader=yaml.SafeLoader ) config = build_config() def get_config(module: str): result = config if module: for name in module.split('.'): result = result[name] if name in result else {} return result