diff --git a/decouple.py b/decouple.py index 9873fc9..238c217 100644 --- a/decouple.py +++ b/decouple.py @@ -188,6 +188,53 @@ def __getitem__(self, key): return self.data[key] +class RepositoryGoogleSecretManager(RepositoryEnv): + """ + Repository class for retrieving configuration options from Google Secret Manager. + + This class extends `RepositoryEnv` to specifically handle configurations stored in + Google Secret Manager. It parses strings formatted in a similar way to `.env` files, + converting them into a dictionary of configuration options. + + Attributes: + data (dict): A dictionary holding the parsed key-value pairs from the Google + Secret Manager source. + """ + + def __init__(self, source): + """ + Initialize RepositoryGoogleSecretManager with a Google Secret Manager source. + + The source string is expected to have one "KEY=value" pair per line, akin to + the `.env` file format. Lines beginning with `#` are treated as comments and + are disregarded. Keys and values are trimmed of surrounding whitespace for + accurate parsing. + + Args: + source (str): The string source from Google Secret Manager to be parsed. + """ + self.data = {} + source_lines = source.split('\n') + + for line in source_lines: + line = line.strip() + + if not line or line.startswith('#') or '=' not in line: + continue + + key, value = line.split('=', 1) + key = key.strip() + value = value.strip() + + if len(value) >= 2 and ( + (value[0] == "'" and value[-1] == "'") + or (value[0] == '"' and value[-1] == '"') + ): + value = value[1:-1] + + self.data[key] = value + + class AutoConfig(object): """ Autodetects the config file and type. diff --git a/tests/test_gsm.py b/tests/test_gsm.py new file mode 100644 index 0000000..e3dde38 --- /dev/null +++ b/tests/test_gsm.py @@ -0,0 +1,105 @@ +# coding: utf-8 +import os +import pytest +from decouple import Config, RepositoryGoogleSecretManager, UndefinedValueError + +ENVSTRING = """ +KeyTrue=True\nKeyOne=1\nKeyYes=yes +KeyY=y +KeyOn=on + +KeyFalse=False +KeyZero=0 +KeyNo=no +KeyN=n +KeyOff=off +KeyEmpty= + +# CommentedKey=None +KeyWithSpaces = Some Value With Spaces +KeyWithQuotes="Quoted Value" +""" + + +@pytest.fixture(scope="module") +def config(): + return Config(RepositoryGoogleSecretManager(ENVSTRING)) + + +def test_string_comment(config): + with pytest.raises(UndefinedValueError): + config("CommentedKey") + + +def test_string_bool_true(config): + assert config("KeyTrue", cast=bool) + assert config("KeyOne", cast=bool) + assert config("KeyYes", cast=bool) + assert config("KeyY", cast=bool) + assert config("KeyOn", cast=bool) + + +def test_string_bool_false(config): + assert not config("KeyFalse", cast=bool) + assert not config("KeyZero", cast=bool) + assert not config("KeyNo", cast=bool) + assert not config("KeyOff", cast=bool) + assert not config("KeyN", cast=bool) + assert not config("KeyEmpty", cast=bool) + + +def test_string_undefined(config): + with pytest.raises(UndefinedValueError): + config("UndefinedKey") + + +def test_string_default_none(config): + assert config("UndefinedKey", default=None) is None + + +def test_string_default_bool(config): + assert not config("UndefinedKey", default=False, cast=bool) + assert config("UndefinedKey", default=True, cast=bool) + + +def test_string_default(config): + assert not config("UndefinedKey", default=False) + assert config("UndefinedKey", default=True) + + +def test_string_default_invalid_bool(config): + with pytest.raises(ValueError): + config("UndefinedKey", default="NotBool", cast=bool) + + +def test_string_empty(config): + assert config("KeyEmpty", default=None) == "" + + +def test_string_support_space(config): + assert config("KeyWithSpaces") == "Some Value With Spaces" + + +def test_string_os_environ(config): + os.environ["KeyOverrideByEnv"] = "This" + assert config("KeyOverrideByEnv") == "This" + del os.environ["KeyOverrideByEnv"] + + +def test_string_undefined_but_present_in_os_environ(config): + os.environ["KeyOnlyEnviron"] = "" + assert config("KeyOnlyEnviron") == "" + del os.environ["KeyOnlyEnviron"] + + +def test_string_empty_string_means_false(config): + assert not config("KeyEmpty", cast=bool) + + +def test_string_repo_keyerror(config): + with pytest.raises(KeyError): + config.repository["UndefinedKey"] + + +def test_string_quoted_value(config): + assert config("KeyWithQuotes") == "Quoted Value"