|
| 1 | +import os |
| 2 | +from pathlib import Path |
| 3 | + |
| 4 | + |
| 5 | +class Required: |
| 6 | + def __init__(self, v_type=None): |
| 7 | + self.v_type = v_type |
| 8 | + |
| 9 | + |
| 10 | +class Settings: |
| 11 | + """ |
| 12 | + Any setting defined here can be overridden by: |
| 13 | +
|
| 14 | + Settings the appropriate environment variable, eg. to override FOOBAR, `export APP_FOOBAR="whatever"`. |
| 15 | + This is useful in production for secrets you do not wish to save in code and |
| 16 | + also plays nicely with docker(-compose). Settings will attempt to convert environment variables to match the |
| 17 | + type of the value here. See also activate.settings.sh. |
| 18 | +
|
| 19 | + Or, passing the custom setting as a keyword argument when initialising settings (useful when testing) |
| 20 | + """ |
| 21 | + _ENV_PREFIX = 'APP_' |
| 22 | + |
| 23 | + def __init__(self, **custom_settings): |
| 24 | + """ |
| 25 | + :param custom_settings: Custom settings to override defaults, only attributes already defined can be set. |
| 26 | + """ |
| 27 | + self._custom_settings = custom_settings |
| 28 | + self.substitute_environ() |
| 29 | + for name, value in custom_settings.items(): |
| 30 | + if not hasattr(self, name): |
| 31 | + raise TypeError('{} is not a valid setting name'.format(name)) |
| 32 | + setattr(self, name, value) |
| 33 | + setattr(self, 'static_path', None) |
| 34 | + |
| 35 | + def substitute_environ(self): |
| 36 | + """ |
| 37 | + Substitute environment variables into settings. |
| 38 | + """ |
| 39 | + for attr_name in dir(self): |
| 40 | + if attr_name.startswith('_') or attr_name.upper() != attr_name: |
| 41 | + continue |
| 42 | + |
| 43 | + orig_value = getattr(self, attr_name) |
| 44 | + is_required = isinstance(orig_value, Required) |
| 45 | + orig_type = orig_value.v_type if is_required else type(orig_value) |
| 46 | + env_var_name = self._ENV_PREFIX + attr_name |
| 47 | + env_var = os.getenv(env_var_name, None) |
| 48 | + if env_var is not None: |
| 49 | + if issubclass(orig_type, bool): |
| 50 | + env_var = env_var.upper() in ('1', 'TRUE') |
| 51 | + elif issubclass(orig_type, int): |
| 52 | + env_var = int(env_var) |
| 53 | + elif issubclass(orig_type, Path): |
| 54 | + env_var = Path(env_var) |
| 55 | + elif issubclass(orig_type, bytes): |
| 56 | + env_var = env_var.encode() |
| 57 | + # could do floats here and lists etc via json |
| 58 | + setattr(self, attr_name, env_var) |
| 59 | + elif is_required and attr_name not in self._custom_settings: |
| 60 | + raise RuntimeError('The required environment variable "{0}" is currently not set, ' |
| 61 | + 'you\'ll need to run `source activate.settings.sh` ' |
| 62 | + 'or you can set that single environment variable with ' |
| 63 | + '`export {0}="<value>"`'.format(env_var_name)) |
0 commit comments