pyinfra/pyinfra/config.py

81 lines
2.1 KiB
Python

"""Implements a config object with dot-indexing syntax."""
import os
from itertools import chain
from operator import truth
from typing import Iterable
from envyaml import EnvYAML
from frozendict import frozendict
from funcy import first, juxt, butlast, last, lmap
from pyinfra.locations import CONFIG_FILE
def _get_item_and_maybe_make_dotindexable(container, item):
ret = container[item]
return DotIndexable(ret) if isinstance(ret, dict) else ret
class DotIndexable:
def __init__(self, x):
self.x = x
def __getattr__(self, item):
return _get_item_and_maybe_make_dotindexable(self.x, item)
def __repr__(self):
return self.x.__repr__()
def __getitem__(self, item):
return self.__getattr__(item)
def __setitem__(self, key, value):
self.x[key] = value
class Config:
def __init__(self, config_path):
self.__config = EnvYAML(config_path)
def __getattr__(self, item):
if item in self.__config:
return _get_item_and_maybe_make_dotindexable(self.__config, item)
def __getitem__(self, item):
return self.__getattr__(item)
def __setitem__(self, key, value):
self.__config.key = value
def to_dict(self):
return to_dict(self.__config.export())
def __hash__(self):
return hash(self.to_dict())
def to_dict(v):
if isinstance(v, list):
return tuple(map(to_dict, v))
elif isinstance(v, DotIndexable):
return frozendict({k: to_dict(v) for k, v in v.x.items()})
elif isinstance(v, dict):
return frozendict({k: to_dict(v) for k, v in v.items()})
else:
return v
CONFIG = Config(CONFIG_FILE)
def parse_disjunction_string(disjunction_string):
def try_parse_env_var(disjunction_string):
try:
return os.environ[disjunction_string]
except KeyError:
return None
options = disjunction_string.split("|")
identifiers, fallback_value = juxt(butlast, last)(options)
return first(chain(filter(truth, map(try_parse_env_var, identifiers)), [fallback_value]))