def test_accessing_non_existant_config_parameter(random_integer): class ConfigSpec(Spec): foo: str class SourceSpec: foo = Default(random_integer) config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) with pytest.raises(ParameterError): config["bar"] with pytest.raises(ParameterError): config.bar assert config.get("bar") is None
def test_aws_paramater(aws_parameter_fixtures, random_string): class ConfigSpec(Spec): foo: str class SourceSpec: foo = aws.Parameter(path="/foo/bar/baz") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) assert config.foo == random_string
def test_vault_secret(vault_secret_fixtures, random_string): class ConfigSpec(Spec): foo: str class SourceSpec: foo = vault.Secret(path="/foo/bar/baz", field="my-secret") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) assert config.foo == random_string
def test_default(environ, random_string): class ConfigSpec(Spec): foo: str class SourceSpec: foo = Default(value=random_string) config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) assert config["foo"] == random_string assert config.foo == random_string
def test_env(environ, random_string): environ["BAR"] = random_string class ConfigSpec(Spec): foo: str class SourceSpec: foo = Env(path="BAR") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) assert config["foo"] == random_string assert config.foo == random_string
def test_preloading_config(environ): """If all sources are ok preloading is successful.""" environ["FOO"] = "123" class ConfigSpec(Spec): foo: str bar: str class SourceSpec: foo = Default(value="FOO") bar = Env(path="FOO") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) preload(config)
def test_integer_parameter_type(environ, random_integer): environ["FOO"] = str(random_integer) environ["BAR"] = str(random_integer) class ConfigSpec(Spec): foo: int bar = Integer() class SourceSpec: foo = Env(path="FOO") bar = Env(path="BAR") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) assert config.foo == random_integer assert config.bar == random_integer
def test_string_parameter_type(environ, random_string): environ["FOO"] = random_string environ["BAR"] = random_string class ConfigSpec(Spec): foo: str bar = String() class SourceSpec: foo = Env(path="FOO") bar = Env(path="BAR") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) assert config.foo == random_string assert config.bar == random_string
def test_get_parameter_info(random_string, random_integer): os.environ["VALUE"] = random_string expected_parameter_info = ParameterInfo("foo", str, ["""Env(path="VALUE")"""]) class ConfigSpec(Spec): foo: str class SourceSpec: foo = Env(path="VALUE") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) info = parameter_info(config, "foo") assert info == expected_parameter_info
def test_adds_entire_doc_to_cache(self, random_string): class MySource(DocumentSource, AbstractSourceDescriptor): def __init__(self, path): self._path = path @property def _name(self): return self._path @property def _key(self): class_name = type(self).__name__ return (class_name, ) def __repr__(self): return f"""MySource(path="{self._path}")""" @property def _doc(self): return { "foo": random_string, "bar": random_string, "baz": random_string, } class ConfigSpec(Spec): foo: str bar: str class SourceSpec: foo = MySource("foo") bar = MySource("bar") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) # call to config.foo fetches doc and stores is in cache assert config.foo == random_string root_cache = getattr(config.__dict__["_Config__source_spec"], "__source_cache__") assert root_cache == { ("MySource", ): { "foo": random_string, "bar": random_string, "baz": random_string, } } assert config.bar == random_string
def test_failed_runtime(environ): """When a source fails at runtime the last fetched value should be kept.""" environ["FOO"] = "FOO" class ConfigSpec(Spec): foo: str class SourceSpec: foo = Env(path="FOO") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) preload(config) assert config.foo == "FOO" del environ["FOO"] assert config.foo == "FOO"
def test_multiple_source_specs(environ, random_string, random_integer): environ["VALUE"] = random_string class ConfigSpec(Spec): foo: str bar: int class SourceSpec1: foo = Env(path="VALUE") class SourceSpec2: bar = Default(value=str(random_integer)) config = Config(config_spec=ConfigSpec, source_spec=(SourceSpec1, SourceSpec2)) assert config.foo == random_string assert config.bar == random_integer
def test_source_spec_from_ini_file(environ, random_string): environ["VALUE"] = str(random_string) tempfile = NamedTemporaryFile(suffix=".ini") with open(tempfile.name, "w") as fh: fh.write( dedent(""" [parameter_foo] source=Env path=VALUE """)) environ["SOURCE_SPEC_PATH"] = tempfile.name class ConfigSpec(Spec): foo: str config = Config(config_spec=ConfigSpec, env_var="SOURCE_SPEC_PATH") assert config.foo == random_string
def test_failed_preloading(environ): """When a source fails at initialisation an error should be thrown.""" try: del environ["FOO"] except KeyError: pass class ConfigSpec(Spec): foo: str bar: str class SourceSpec: foo = Default(value="FOO") bar = Env(path="FOO") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) with pytest.raises(Exception): preload(config)
def test_multiple_source_specs_most_significant_spec(environ, random_string, random_integer): environ["VALUE"] = random_string class ConfigSpec(Spec): foo: str bar: str class SourceSpec1: foo = Env(path="VALUE") bar = Default(random_integer) class SourceSpec2: bar = Default(value=str(random_integer)) config = Config(config_spec=ConfigSpec, source_spec=(SourceSpec1, SourceSpec2)) # SourceSpec1 shadows SourceSpec2 assert config.foo == random_string assert config.bar == str(random_integer)
def test_get_all_parameters_info(random_string, random_integer): os.environ["VALUE"] = random_string os.environ["VALUE"] = random_string expected_parameter_info = [ ParameterInfo("foo", str, ["""Env(path="VALUE")"""]), ParameterInfo("bar", str, ["""Env(path="VALUE")"""]), ] class ConfigSpec(Spec): foo: str bar: str class SourceSpec: foo = Env(path="VALUE") bar = Env(path="VALUE") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) info = all_parameter_info(config) assert info == expected_parameter_info
def test_dotfile(random_string, random_integer): tempfile = NamedTemporaryFile(prefix=".env") with open(tempfile.name, "w") as fh: fh.write( dedent(f""" # a comment and that will be ignored. FOO={random_string} BAR={random_integer} """)) class ConfigSpec(Spec): foo: str bar: int class SourceSpec: foo = files.DotEnvFile(path="FOO", dotenv_path=tempfile.name) bar = files.DotEnvFile(path="BAR", dotenv_path=tempfile.name) config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) assert config.foo == random_string assert config.bar == random_integer
def test_source_spec_from_multiple_ini_file(environ, random_string, random_integer): environ["VALUE"] = str(random_string) deploy_spec = NamedTemporaryFile(suffix=".ini") default_spec = NamedTemporaryFile(suffix=".ini") with open(deploy_spec.name, "w") as fh: fh.write( dedent(f""" [parameter_foo] source=Env path=VALUE """)) with open(default_spec.name, "w") as fh: fh.write( dedent(f""" [parameter_foo] source=Default value=Should not be read! [parameter_bar] source=Default value={random_integer} """)) # deploy_spec shadows default_spec environ["SOURCE_SPEC_PATH"] = ",".join( (deploy_spec.name, default_spec.name)) class ConfigSpec(Spec): foo: str bar: str config = Config(config_spec=ConfigSpec, env_var="SOURCE_SPEC_PATH") # deploy_spec shadows default_spec assert config.foo == random_string assert config.bar == str(random_integer)
def test_casts_source_value_to_type(environ, random_integer): environ["VALUE"] = str(random_integer) class ConfigSpec(Spec): foo: str bar: int xor = String() baz = Integer() class SourceSpec: foo = Env(path="VALUE") bar = Env(path="VALUE") xor = Env(path="VALUE") baz = Env(path="VALUE") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) assert type(config.foo) == str assert config.foo == str(random_integer) assert type(config.bar) == int assert config.bar == random_integer assert type(config.xor) == str assert config.foo == str(random_integer) assert type(config.baz) == int assert config.bar == random_integer
# Add repo root to path to make config_composer importable repo_root = str(Path(__file__).absolute().parent.parent.parent) sys.path.append(repo_root) from config_composer.core import Config, Spec # noqa: E402s logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class ConfigSpec(Spec): host: str port: int db_pass: str config = Config(config_spec=ConfigSpec, env_var="SOURCE_SPEC_PATH") def mock_query_db(): # password = config.db_pass return "Bob" class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): def do_GET(self): username = mock_query_db().encode() self.send_response(200) self.end_headers() self.wfile.write(b"Hello, " + username)
from config_composer.core import Spec, Config from config_composer.core.utils import preload from config_composer.sources.env import Env from config_composer.sources import aws, vault, files from config_composer.sources.default import Default class ConfigSpec(Spec): foo: str bar: int baz: str class SourceSpec: foo = Env("FOO") bar = aws.Parameter(path="/foo/bar/baz") baz = vault.Secret(path="/foo/bar/baz", field="my-secret") qux = files.DotEnvFile(path="FOO", dotenv_path="/app/config/.prod-env") wat = Default(value="wat") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) if __name__ == "__main__": preload(config) env_foo = config.foo bar_foo = config["bar"] env_baz = config.get("baz")
def test_adds_entire_doc_to_cache(self, random_string): control = {"expired": True} class MySource(DocumentSource, DocumentSourceTTL, AbstractSourceDescriptor): def __init__(self, path): self._path = path self._count = 0 @property def _name(self): return self._path @property def _key(self): class_name = type(self).__name__ return (class_name, ) def __repr__(self): return f"""MySource(path="{self._path}")""" @property def _doc(self): self._count += 1 return { "foo": f"foo - {random_string} - {self._count}", "bar": f"bar - {random_string} - {self._count}", "baz": f"baz - {random_string} - {self._count}", } def _expired(self, ttl_data): if not isinstance(ttl_data, dict): ttl_data = control if ttl_data["expired"] is True: expired = True ttl_data.update({"expired": False}) else: expired = False return expired, ttl_data class ConfigSpec(Spec): foo: str bar: str class SourceSpec: foo = MySource("foo") bar = MySource("bar") config = Config(config_spec=ConfigSpec, source_spec=SourceSpec) # call to config.foo fetches doc and stores is in cache assert config.foo == f"foo - {random_string} - 1" assert config.bar == f"bar - {random_string} - 1" root_cache = getattr(config.__dict__["_Config__source_spec"], "__source_cache__") assert root_cache == { ("MySource", ): { "foo": f"foo - {random_string} - 1", "bar": f"bar - {random_string} - 1", "baz": f"baz - {random_string} - 1", } } # call to config.foo fetches doc and stores is in cache source_spec = config.__dict__["_Config__source_spec"] control["expired"] = True assert config.foo == f"foo - {random_string} - 2" assert config.bar == f"bar - {random_string} - 2" root_cache = getattr(source_spec, "__source_cache__") assert root_cache == { ("MySource", ): { "foo": f"foo - {random_string} - 2", "bar": f"bar - {random_string} - 2", "baz": f"baz - {random_string} - 2", } }