Skip to content

Commit

Permalink
make DOTENV setting more robust
Browse files Browse the repository at this point in the history
Add additional config options to DOTENV while retaining compatibility with the old way of setting it
  • Loading branch information
P1roks committed Nov 11, 2024
1 parent cad6dcb commit bc11925
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 3 deletions.
22 changes: 20 additions & 2 deletions configurations/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def OTHER(self):
"""
DOTENV_LOADED = None
DOTENV_RELOAD = False

@classmethod
def load_dotenv(cls):
Expand All @@ -109,6 +110,18 @@ def load_dotenv(cls):
# check if the class has DOTENV set whether with a path or None
dotenv = getattr(cls, 'DOTENV', None)

required = True
override_env = False
# check if the DOTENV is dict, and check all options of it
if isinstance(dotenv, dict):
# whether we want to override previously set envs
override_env = dotenv.get("override", False)
# whether we want to error if the file is not found
required = dotenv.get("required", True)
# whether we want to reload on dotenv, useful if we want to frequently change it
cls.DOTENV_RELOAD = dotenv.get("reload", False)
dotenv = dotenv.get("path", None)

# if DOTENV is falsy we want to disable it
if not dotenv:
return
Expand All @@ -118,6 +131,8 @@ def load_dotenv(cls):
with open(dotenv, 'r') as f:
content = f.read()
except OSError as e:
if not required:
return
raise ImproperlyConfigured("Couldn't read .env file "
"with the path {}. Error: "
"{}".format(dotenv, e)) from e
Expand All @@ -133,13 +148,16 @@ def load_dotenv(cls):
m3 = re.match(r'\A"(.*)"\Z', val)
if m3:
val = re.sub(r'\\(.)', r'\1', m3.group(1))
os.environ.setdefault(key, val)
if override_env:
os.environ[key] = val
else:
os.environ.setdefault(key, val)

cls.DOTENV_LOADED = dotenv

@classmethod
def pre_setup(cls):
if cls.DOTENV_LOADED is None:
if cls.DOTENV_LOADED is None or cls.DOTENV_RELOAD:
cls.load_dotenv()

@classmethod
Expand Down
14 changes: 14 additions & 0 deletions docs/cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ A ``.env`` file is a ``.ini``-style file. It must contain a list of
API_KEY1=1234
API_KEY2=5678
``DOTENV`` can also be a dictionary, and then its behavior can be configured more:
.. code-block:: python
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
class Dev(Configuration):
DOTENV = {
"path": str(os.path.join(BASE_DIR, '.env')),
# if True, overriddes previously set environmental variables, if False only sets them if they haven't been set before
"override": True,
# if True errors if the DOTENV is not found at path, if False return
"required": False,
# if True, reloads DOTENV dynamically for example on hot reload
"reload": True,
Envdir
------
Expand Down
3 changes: 2 additions & 1 deletion test_project/.env
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
DJANGO_DOTENV_VALUE='is set'
DJANGO_DOTENV_VALUE='is set'
DJANGO_DOTENV_OVERRIDE='overridden'
12 changes: 12 additions & 0 deletions tests/settings/dot_env_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from configurations import Configuration, values


class DotEnvConfiguration(Configuration):

DOTENV = {
'path': 'test_project/.env',
'override': True,
}

DOTENV_VALUE = values.Value()
DOTENV_OVERRIDE = values.Value("Not overridden")
12 changes: 12 additions & 0 deletions tests/settings/dot_env_not_required.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from configurations import Configuration, values


class DotEnvConfiguration(Configuration):

DOTENV = {
'path': 'some_nonexistant_path',
'override': True,
'required': False,
}

DOTENV_OVERRIDE = values.Value("Not overridden")
15 changes: 15 additions & 0 deletions tests/test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,18 @@ 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)

@patch.dict(os.environ, clear=True,
DJANGO_CONFIGURATION='DotEnvConfiguration',
DJANGO_SETTINGS_MODULE='tests.settings.dot_env_dict')
def test_env_dict(self):
from tests.settings import dot_env_dict
self.assertEqual(dot_env_dict.DOTENV_VALUE, 'is set')
self.assertEqual(dot_env_dict.DOTENV_OVERRIDE, 'overridden')

@patch.dict(os.environ, clear=True,
DJANGO_CONFIGURATION='DotEnvConfiguration',
DJANGO_SETTINGS_MODULE='tests.settings.dot_env_not_required')
def test_env_not_required(self):
from tests.settings import dot_env_not_required
self.assertEqual(dot_env_not_required.DOTENV_OVERRIDE, 'Not overridden')

0 comments on commit bc11925

Please sign in to comment.