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.