diff --git a/.gitignore b/.gitignore index 8c57199..03bc604 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ build/ .tox/ htmlcov/ *.pyc -dist/ \ No newline at end of file +dist/ diff --git a/configurations/base.py b/configurations/base.py index 8601c73..11cd630 100644 --- a/configurations/base.py +++ b/configurations/base.py @@ -1,3 +1,5 @@ +import os +import re import warnings from django.utils import six @@ -66,9 +68,53 @@ class Configuration(six.with_metaclass(ConfigurationBase)): to the name of the class. """ + DOTENV_LOADED = None + + @classmethod + def load_dotenv(cls): + """ + Pulled from Honcho code with minor updates, reads local default + environment variables from a .env file located in the project root + or provided directory. + + http://www.wellfireinteractive.com/blog/easier-12-factor-django/ + https://gist.github.com/bennylope/2999704 + """ + # check if the class has DOTENV set wether with a path or None + dotenv = getattr(cls, 'DOTENV', None) + + # if DOTENV is falsy we want to disable it + if not dotenv: + return + + # now check if we can access the file since we know we really want to + try: + with open(dotenv, 'r') as f: + content = f.read() + except IOError as e: + raise ImproperlyConfigured("Couldn't read .env file " + "with the path {}. Error: " + "{}".format(dotenv, e)) + else: + for line in content.splitlines(): + m1 = re.match(r'\A([A-Za-z_0-9]+)=(.*)\Z', line) + if not m1: + continue + key, val = m1.group(1), m1.group(2) + m2 = re.match(r"\A'(.*)'\Z", val) + if m2: + val = m2.group(1) + m3 = re.match(r'\A"(.*)"\Z', val) + if m3: + val = re.sub(r'\\(.)', r'\1', m3.group(1)) + os.environ.setdefault(key, val) + + cls.DOTENV_LOADED = dotenv + @classmethod def pre_setup(cls): - pass + if cls.DOTENV_LOADED is None: + cls.load_dotenv() @classmethod def post_setup(cls): @@ -88,4 +134,4 @@ class Settings(Configuration): # make sure to remove the handling of the Settings class above when deprecating warnings.warn("configurations.Settings was renamed to " "settings.Configuration and will be " - "removed in 1.0", PendingDeprecationWarning) + "removed in 1.0", DeprecationWarning) diff --git a/test_project/.env b/test_project/.env new file mode 100644 index 0000000..507755e --- /dev/null +++ b/test_project/.env @@ -0,0 +1 @@ +DJANGO_DOTENV_VALUE='is set' \ No newline at end of file diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py index 334cee6..1ffe526 100644 --- a/test_project/test_project/settings.py +++ b/test_project/test_project/settings.py @@ -160,20 +160,6 @@ class Base(Configuration): } } - INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - # Uncomment the next line to enable the admin: - # 'django.contrib.admin', - # Uncomment the next line to enable admin documentation: - # 'django.contrib.admindocs', - 'configurations', - ) - class Debug(Base): YEAH = True diff --git a/tests/settings/dot_env.py b/tests/settings/dot_env.py new file mode 100644 index 0000000..eab4237 --- /dev/null +++ b/tests/settings/dot_env.py @@ -0,0 +1,8 @@ +from configurations import Configuration, values + + +class DotEnvConfiguration(Configuration): + + DOTENV = 'test_project/.env' + + DOTENV_VALUE = values.Value() diff --git a/tests/settings/main.py b/tests/settings/main.py index dd425fd..d646271 100644 --- a/tests/settings/main.py +++ b/tests/settings/main.py @@ -3,9 +3,13 @@ import uuid import django from configurations import Configuration, pristinemethod +from configurations.values import BooleanValue class Test(Configuration): + + ENV_LOADED = BooleanValue(False) + DEBUG = True SITE_ID = 1 diff --git a/tests/test_env.py b/tests/test_env.py new file mode 100644 index 0000000..47286d0 --- /dev/null +++ b/tests/test_env.py @@ -0,0 +1,14 @@ +import os +from django.test import TestCase +from mock import patch + + +class DotEnvLoadingTests(TestCase): + + @patch.dict(os.environ, clear=True, + DJANGO_CONFIGURATION='DotEnvConfiguration', + DJANGO_SETTINGS_MODULE='tests.settings.dot_env') + def test_env_loaded(self): + from tests.settings import dot_env + self.assertEqual(dot_env.DOTENV_VALUE, 'is set') + self.assertEqual(dot_env.DOTENV_LOADED, dot_env.DOTENV)