Did you ever ask yourself how to separate your sensitive information, like the secret key, from your project? Well Python Decouple is the right tool for that!
Python Decouple is a package that was originally designed for Django that helps you manage the settings of an application without having to redeploy it.
If you are a developer you must have this tool in your toolbox.
To start with a new project open the terminal and run these commands:
$ mkdir myapp && cd $_
$ python3 -m venv venv
$ source venv/bin/activate
(venv) $ pip install django
(venv) $ django-admin startproject mysite .
First, let's using pip to install Python Decouple:
$ pip install python-decouple
Now we need to use the config function from the decouple module in our settings.py file:
# mysite/settings.py
from pathlib import Path
from decouple import config
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG')
# ...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': config('NAME'),
'USER': config('USER'),
'PASSWORD': config('PASSWORD'),
'HOST': config('HOST'),
'PORT': '',
}
}
EMAIL_HOST = config('EMAIL_HOST')
EMAIL_PORT = config('EMAIL_PORT')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_USE_TLS = config('EMAIL_USE_TLS')
# ...
This way Python Decouple will try to find these parameters in the .ini or .env file and if a parameter is not present in the file and does not have a default value (we will see later how to do it) it will raise an UndefinedValueError
.
Now, we need to put all these variables in the .env, go ahead and create it at the root directory of the project (at the same level as manage.py):
SECRET_KEY=django-insecure-!wo*h78gl1t3x=exnf2e1djx0f7lg!a2ax1p(hv=dd%8(g3db
DEBUG=True
NAME=your-db-name
USER=your-db-username
PASSWORD=your-db-password
HOST=your-db-host
EMAIL_HOST=your-email-host
EMAIL_PORT=your-email-port
EMAIL_HOST_PASSWORD=your-email-host-password
EMAIL_HOST_USER=your-email-host-user
Note that Python Decouple will return strings values. However, some parameters need to be Boolean (like DEBUG), so how can we do that?
That is what the cast argument is used for.
You can add the cast argument to the config function to specify which value do you want to return.
Let's have a look at how it works:
print(type(config('DEBUG')))
<class 'str'>
print(type(config('DEBUG', cast=bool)))
<class 'bool'>
So the above configuration will become:
# mysite/settings.py
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', cast=bool)
# ...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': config('NAME'),
'USER': config('USER'),
'PASSWORD': config('PASSWORD'),
'HOST': config('HOST'),
'PORT': '',
}
}
EMAIL_HOST = config('EMAIL_HOST')
EMAIL_PORT = config('EMAIL_PORT', cast=int)
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_USE_TLS = config('EMAIL_USE_TLS', cast=bool)
Please refer to the documentation to see what type of value the cast argument can receive.
Imagine this case: In the development phase you want your DEBUG variable to be set to True while in production you want it to be set to False, can we do that without having to separate our configuration? The answer is YES!
The config function takes default as an extra argument to give default values in case there is an undefined value in the .env file.
Like I said above Python Decouple will try to find a value in the environment variables, if he can't find it, it will search it in either .ini or .env, if the value is not defined, it will search in the default argument.
Here is how you can do it:
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=True, cast=bool)
# ...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': config('NAME', default='rouizi-db'),
'USER': config('USER', default='rouizi'),
'PASSWORD': config('PASSWORD', default='1234'),
'HOST': config('HOST'),
'PORT': '',
}
}
EMAIL_HOST = config('EMAIL_HOST', default='localhost')
EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int)
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='')
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool)
So in the development phase, if you don't put, for example, DEBUG in the .env file, Python Decouple will grab the value from the default argument.
But don't forget to specify this value in production, otherwise, Python Decouple will use the default one.
One last thing: you have to add the .env file to your .gitignore file so you don't push this sensitive information to your repository.
In this tutorial, we saw how to use Python Decouple to organize your settings and how to store them in a .env file. We also saw how to convert the values to the correct data type and how to give them a default value. Also, we learned that with Python Decouple you don't need different configuration files for development and production.
If you liked this tutorial don't forget to leave a comment below. See you in the next one.
@Damjan Dimitrioski I didn't use Vault by HashiCorp before but I think it's possible to use it instead of Decouple. But from what I read on their documentation, you have to install their library in order to make an API call to retrieve your API keys, secret key, or other stuff. I think their solution is more secure because you don't have to store plain text in files, like with Decouple.
May 21, 2021, 3:25 p.m.
@Avi I don't think that python decouple cache the env variables, maybe you just forget to save your file after changing your env variables?
May 30, 2022, 7:15 p.m.
May 19, 2021, 9:56 a.m.