Working with Credentials and Configurations in Python
11 Jan 2022Table of Contents
- Python Configuration Files
- Environment Variables
- Python Dotenv
- JavaScript Object Notation (JSON)
- Yet Another Markup Language (YAML)
- Using a Configuration Parser
- Parsing Command-line Options
- Conclusion
- Resources
When writing programs, there is often a large set of configuration and credentials that should not be hard-coded in the program. This also makes the customization of the program much easier and more generally applicable. There are various ways to handle configuration and credentials and you will see here a few of the popular and common ways to do that with Python.
One important note right from the start: When using version control always make sure to not commit credentials and configuration into the repository as this could become a serious security issue. You can add those to .gitignore to avoid pushing those files to version control. Sometimes is useful to have general configuration also in version control, but that depends on your use case.
Python Configuration Files
The first and probably most straight forward way is to have a config.py
file somewhere in the project folder that you add to your .gitignore
file. A similar pattern can be found in Flask, where you can also structure the configuration based on different contexts like development, production, and testing. The config.py
would look something like:
host = 'localhost',
port = 8080,
username = 'user'
password = 'password'
You would simply import it and use it like this:
import config
host = config.host
port = config.port
username = config.username
password = config.password
Environment Variables
You can access environment variables with os.environ:
import os
os.environ['SHELL']
This will throw a KeyError
if the variable does not exists. You can check if the variable exists with "SHELL" in os.environ
. Sometimes its more elegant to get None
or a default value instead of getting an error when a variable does not exist. This can be done like this:
# return None if VAR does not exists
os.environ.get('VAR')
# return "default" if VAR does not exists
os.environ.get('VAR', "default")
You can combine this with the previous way to have a config.py
with the following contents:
import os
host = os.environ.get('APP_HOST', 'localhost')
port = os.environ.get('APP_PORT', 8080)
username = os.environ.get('APP_USERNAME')
password = os.environ.get('APP_PASSWORD')
Python Dotenv
Oftentimes you want to have the environment variables in a dedicated .env
file outside of version control. One way is to load the file before with:
source .env
This is sometimes error-prone or not possible depending on the setup, so its sometimes better to load the file dynamically with python-dotenv. You can install the package with:
pip install -U python-dotenv
Load the .env
file in your program with:
from dotenv import load_dotenv
load_dotenv()
If your environment file is located somewhere else, you can load it with:
load_dotenv("/path/to/.env")
Now, you can use the environment file as you saw before.
JavaScript Object Notation (JSON)
JSON is another handy file format to store your configuration as it has native support. If you are working with frontend code, you are already familiar with its usefulness and ubiquity.
You can prepare your configurations as a JSON (JavaScript Object Notation) in a config.json
with the following example configuration:
{
"host": "localhost",
"port": 8080,
"credentials": {
"username": "user",
"password": "password"
}
}
You can load this configuration then with the built-in json
package:
import json
with open('config.json', 'r') as f:
config = json.load(f)
This returns the data as (nested) dictionaries and lists which you can access the way you are used to (config['host']
or config.get('host')
).
Yet Another Markup Language (YAML)
Another popular way to store configurations and credentials is the (in)famous YAML format. It is much simpler to use but has some minor quirks when using more complicated formatting. Here is the previous configuration as a YAML file:
host: localhost
port: 8080
credentials:
username: user
password: password
There are various packages that you can use. Most commonly PyYAML. You can install it with:
pip install -U PyYAML
To load the configuration, you can type:
with open("config.yml", 'r') as f:
config = yaml.load(f, Loader=yaml.FullLoader)
The config
can be used as previously seen with the JSON example.
Note, that you need to add a Loader
in PyYAML 5.1+
because of a vulnerability. Read more about it here. Another common alternative to PyYAML
is omegaconf, which includes many other useful parsers for various different file types.
Using a Configuration Parser
The Python standard library includes the configparser module which can work with configuration files similar to the Microsoft Windows INI files. You can prepare the configuration in config.ini
with the following contents:
[DEFAULT]
host = localhost
port = 8080
[credentials]
username = user
password = password
The configuration is seperated into sections like [credentials]
and within those sections the configuration is stored as key-value pairs like host = localhost
.
You can load and use the previous configuration as follows:
import configparser
config = configparser.ConfigParser()
config.read("test.ini")
host = config['DEFAULT']['host']
port = config['DEFAULT']['port']
username = config['credentials']['username']
password = config['credentials']['password']
As you can see, to access the values you have to type config[section][element]
. To get all sections as a list, you can type config.sections()
. For more information, have a look at the documentation.
Parsing Command-line Options
It is also possible to get credentials and configuration through arguments by using the built-in argparse module.
You can initialize the argument parser with:
import argparse
parser = argparse.ArgumentParser(
description="Example Program")
# Required arguments
parser.add_argument(action='store',
dest='username', help="session username")
parser.add_argument(action='store',
dest='password', help="session password")
# Optional arguments with default values
parser.add_argument("-H", "--host", action='store',
dest='host', default="localhost",
help="connection host")
# Allow only arguments of type int
parser.add_argument("-P", "--port", action='store',
dest='port', default=8080, type=int,
help="connection port")
Now, you can parse the arguments with:
args = parser.parse_args()
host = args.host
port = args.port
username = args.username
password = args.password
If you save this program in example.py
and type python example.py -h
, you will receive the following help description:
usage: untitled.py [-h] [-H HOST] [-P PORT] username password
Example Program
positional arguments:
username session username
password session password
optional arguments:
-h, --help show this help message and exit
-H HOST, --host HOST connection host
-P PORT, --port PORT connection port
Another alternative to argparse
is typer which makes some of the parsing easier for complex CLI tools.
Conclusion
Here you saw a few common and popular ways to load configuration and credentials in Python, but there are many more ways if those are not sufficient for your usecase. You can always resort to XML if you really wish. If you miss some way that you particularly find useful, feel free to add it in the comments bellow.
Resources
- 2014 - Configuration files in Python
- How To Read and Set Environmental and Shell Variables on a Linux VPS
- Github - theskumar/python-dotenv
- Github - omry/omegaconf
- Github - tiangolo/typer