Fetch Settings From Environment Variables
A popular thing in web development is the twelve-factor app methodology. Item #3 of 12 asks us to store config in the environment.
In Django most of this is in your settings.py
or settings module and a common mistake is to hard-code your settings and push it to version control. This is not a good idea, because this implies everyone is going to use whatever settings your production environments are using in all the other environments. The production environment should only use the production database. The testing and staging environments should only use their own databases. Development machines should not talk to production databases.
Python’s OS module
There are two easy ways to get environment variables using Python’s os module.
The first one is using os.environ
which looks and behaves like a Python dictionary, but technically isn’t. For practicality and for the purpose of this guide, we will limit our use of os.environ
to that of a Python dictionary.
Let’s say we want to fetch our database settings from our environment variables. Django works best with Postgres so let’s use that for all our envs and since there’s no variability there then there’s no need for an environment variable. For the other things, let’s fetch those from environment variables.
It’s important to note that if we try to use a key that doesn’t exist in os.environ
a KeyError
is raised.
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['DB_NAME'],
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASSWORD'],
'HOST': os..environ['DB_HOST'],
'PORT': '5432',
}
}
The other easy way of fetching environment variables is using os.getenv
which behaves the same way as calling .get()
on a dictionary instance. If the key doesn’t exist, then it returns None
. It’s also possible to set a default value in case the environment variable doesn’t exist.
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv('DB_NAME'),
'USER': os.getenv('DB_USER'),
'PASSWORD': os.getenv('DB_PASSWORD'),
'HOST': os.getenv('DB_HOST'),
'PORT': os.getenv('DB_PORT', default='5432')
}
}
The problem with settings.DEBUG
It is tempting to directly fetch the value of settings.DEBUG
using Python’s os
module. The problem is that both os.environ
and os.getenv
return strings so any non-empty string like "False"
assigned to settings.DEBUG
is still truthy.
One work-around is to compare the value of your environment variable for DEBUG
and use the result of that comparison as the value for settings.DEBUG
DEBUG = os.getenv('DEBUG', default="False") == "True"
This works, but feels a bit awkward and you need to keep track of which things are strings and which things are booleans.
python-environ and settings.DEBUG
Similar to most problems in web development, we’re not the first ones to run into this problem so someone else has already solved it for us. In the case of python-environ the most common use case looks and feels like using os.getenv
, but it also simplifies things like fudging around with types for settings.DEBUG
and needing only a single environment variable instead of multiple environment variables for settings.DATABASES
.
import environ
env = environ.Env(
DEBUG=(bool, False)
)
DEBUG = env('DEBUG')
In the example above, env('DEBUG')
returns a boolean True
if your environment variable named DEBUG
is set to "True"
or "true"
and a boolean False
if your environment variable named DEBUG
is set to "False"
or "false"
. Other strings like "on"
, "off"
, "TRUE"
, etc still give the expected result. While this opens up a lot of possibilities it’s better to exercise caution and use reasonable values.
If you found this useful and would like to get updates on new posts, please subscribe to our newsletter and follow us on Twitter @DjangoCookbook.