Using Environment Variables in Django

An environment variable is a variable whose value is set outside the program, typically through a functionality built into the operating system. An environment variable is made up of a name/value pair.

While working with web applications often we need to store sensitive data for authentication of different modules such as database credentials, password, secret key, debug status, email host, allowed hosts and API keys. These sensitive keys should not be hardcoded in the settings.py file instead they should be loaded with Environment variables on runtime.

Mostly, In a development environment we runs our application with debug mode on. Also, it’s a good idea to keep the secret key in a safe place (not in your git repository).

In this blog, we will learn about the python decouple library.

Python Decouple

Python Decouple is a great library that helps you strictly separate the settings parameters from your source code. The idea is simple: Parameters related to the project, goes straight to the source code. Parameters related to an instance of the project, goes to an environment file.

Why should use python decouple?

The settings files in web frameworks store many different kinds of parameters:

  • Locale and i18n;
  • Middlewares and Installed Apps;
  • Resource handles to the database, Memcached, and other backing services;
  • Credentials to external services such as Amazon S3 or Twitter;
  • Per-deploy values such as the canonical hostname for the instance.

The first 2 are project settings and the last 3 are instance settings.

You should be able to change instance settings without redeploying your app.

Why not just use environment variables?

Envvars works, but since os.environ only returns strings, it’s tricky.

Let’s say you have an envvar DEBUG=False. If you run:

if os.environ['DEBUG']:
    print(True)
else:
    print(False)

It will print True, because os.environ['DEBUG'] returns the string “False”. Since it’s a non-empty string, it will be evaluated as True.

Decouple provides a solution that doesn’t look like a workaround: config('DEBUG', cast=bool).


Let’s start, Django Environment Variables

Installation

$ pip install python-decouple

Or download it from PyPI if you prefer.

Add decouple app into the INSTALLED_APPS

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Python Decouple app
    'decouple'
]

Usage

Let’s consider the following settings.py file, to explain how to use the library.

import os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = '3izb^ryglj(bvrjb2_y1fZvcnbky#358_l6-nn#i8fkug4mmz!'
DEBUG = True
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'DB_NAME',
        'USER': 'DB_USER',
        'PASSWORD': 'DB_PASS',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

Creating Environment Variables

First create a file named .env in the root of your project where manage.py file resides and add the following key-value pair inside the file.. You can also use a .ini file, in case the .env isn’t suitable for your use case. See the documentation for further instructions.

SECRET_KEY=3izb^ryglj(bvrjb2_y1fZvcnbky#358_l6-nn#i8fkug4mmz!
DEBUG=True
DB_NAME=DB_NAME
DB_USER=DB_USER
DB_PASS=DB_PASS
DB_HOST=127.0.0.1
DB_PORT=5432

Note: If you are working with Git, update your .gitignore adding the .env file so you don’t commit any sensitive data to your remote repository. It’s advisable to create a .env.example with a template of all the variables required for the project.

Access the variables

Import the library

from decouple import config

Retrieve the settings parameters:

import os
from decouple import config

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', cast=bool)

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': config('DB_NAME'),
        'USER': config('DB_USER'),
        'PASSWORD': config('DB_PASS'),
        'HOST': config('DB_HOST'),
        'PORT': config('DB_HOST'),
    }
}

Casting the data

Attention to the cast argument. Django expects DEBUG to be a boolean. In a similar way it expects EMAIL_PORT to be an integer.

DEBUG = config('DEBUG', cast=bool)
EMAIL_PORT = config('EMAIL_PORT', cast=int)

Actually, the cast argument can receive any callable, that will transform the string value into something else. In the case of the ALLOWED_HOSTS, Django expects a list of hostname.

In the .env file you can put it like this:

ALLOWED_HOSTS=.localhost, skillshats.com, 127.0.0.1

And then in the settings.py you can retrieve it this way:

ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=lambda v: [s.strip() for s in v.split(',')])

It’s looks little bit complicated, right? Actually the library comes with a Csv Helper, so you don’t need to write all this code. The better way to do it would be:

from decouple import config, Csv

ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())

Choices

Allows for cast and validation based on a list of choices. For example:
.env file

CONNECTION_TYPE=usb

In setting file

CONNECTION_TYPE = config('CONNECTION_TYPE', cast=Choices(['eth', 'usb', 'bluetooth']))

If you set and random value for the CONNECTION_TYPE, that does not exists in the list, Then it’ll throw an ValueError

CONNECTION_TYPE=serial
config('CONNECTION_TYPE', cast=Choices(['eth', 'usb', 'bluetooth']))
Traceback (most recent call last):
 ...
ValueError: Value not in list: 'serial'; valid values are ['eth', 'usb', 'bluetooth']

You can also use a Django-like choices tuple:


USB = 'usb'
ETH = 'eth'
BLUETOOTH = 'bluetooth'

CONNECTION_OPTIONS = (
    (USB, 'USB'),
    (ETH, 'Ethernet'),
    (BLUETOOTH, 'Bluetooth'),
)

CONNECTION_TYPE = config('CONNECTION_TYPE', cast=Choices(choices=CONNECTION_OPTIONS))

Default values

You can add an extra argument to the config function, to define a default value, in case there is an undefined value in the .env file.

DEBUG = config('DEBUG', default=True, cast=bool)

Meaning you won’t need to define the DEBUG parameter in the .env file in the development environment for example.

Overriding config files

In case you want to temporarily change some of the settings parameter, you can override it with environment variables:

DEBUG=False python manage.py

Load .env file outside the expected paths

Let’s see, how do you use python-decouple to load .env file outside the expected paths or custom paths?

  • Instead of importing decouple.config and doing the usual config('KEY_NAME')
  • Create a new decouple.Config object using RepositoryEnv('/path/to/env-file')
from decouple import Config, RepositoryEnv

DOTENV_FILE = '/opt/envs/project-name/.env'
env_config = Config(RepositoryEnv(DOTENV_FILE))

# use the Config().get() method as you normally would since 
# decouple.config uses that internally. 
# i.e. config('SECRET_KEY') = env_config.get('SECRET_KEY')

SECRET_KEY = env_config.get('SECRET_KEY')

Python Decouple

Explore More Django Posts

Efficient Django Project Settings with Split Settings Library

Learn how to efficiently manage your Django project settings with the Split Settings library. Use environment variables, keep sensitive information i…

Read More
Integrating Flake8 with Django: Best Practices

Learn how to integrate Flake8 with Django projects and enforce code quality. Follow our step-by-step guide and optimize your Django workflow with Fla…

Read More
Django Authentication and Authorization with JWT

Learn how to implement JSON Web Token (JWT) based authentication and authorization in Django web applications with step-by-step guide and code exampl…

Read More
Best Practices for Django Development: Tips and Tricks

Learn the best practices for Django development, including project structure, code organization, testing, and deployment. Build high-quality web apps.

Read More
Django Middleware: Tips, Tricks and Examples

Learn how to use Django Middleware to improve your app's performance and security. Includes examples and best practices.

Read More
Django Production Deployment: Best Practices & Checklist

Learn the best practices and checklist for deploying a Django application to production. Includes tips on web servers, databases, caching, security, …

Read More