You've successfully subscribed to Better Data Science
Great! Next, complete checkout for full access to Better Data Science
Welcome back! You've successfully signed in
Success! Your account is fully activated, you now have access to all content.

Stop Hardcoding Values in Python Apps - Use ConfigParser Instead

Stop Hardcoding Values in Python Apps - Use ConfigParser Instead

Hardcoded values are a terrible idea. Use configparser to read .ini configuration files instead

Hardcoding configuration values in Python apps is fun and games until something breaks. No one likes searching and replacing values in hundreds of files just due to a small API endpoint URL change. Luckily, there's a way around it - and it's by writing app configuration in a separate file.

Today you'll learn all about working with .ini files in Python. I find them to provide more flexibility than JSON or YAML, but just a tad worse than Python-based configuration files. Why? Well, conditional logic, for starters.

Up next, you'll see how to install and use the configparser Python library with .ini configuration files in Python.


How to Install ConfigParser Python Library

You'll need the configparser module to work with .ini configuration files. It doesn't ship with Python, so you'll have to install it. The installation command varies, depending on whether you're using Pip or Anaconda. Here's an installation command for both:

Pip:

pip install configparser

Anaconda:

conda install -c conda-forge -y configparser

And that's all we need to get started working with .ini files in Python.


How to Read .ini Configuration Files with ConfigParser

I've created a new folder with two files:

  • app.py - Contains Python code for today.
  • config.ini - Contains configuration data we don't want to hardcode in Python scripts.

As for the contents of the .ini file, it's best to organize it into sections. Below you'll see two sections with the same keys and different values. The goal is to use different file save locations in different environments. For example, when in the development environment, the file will be saved to Desktop, while in production the same file will be saved in the temp directory:

[dev]
api_url = https://gorest.co.in/public/v2/users
save_dir = /Users/dradecic/Desktop/users_data
save_file = users.json

[prod]
api_url = https://gorest.co.in/public/v2/users
save_dir = /tmp/users_data
save_file = users.json

But how can you read this file in Python? The configparser module makes it incredibly easy. Just make a new instance of the ConfigParser class and read the .ini file:

import configparser


config = configparser.ConfigParser()
config.read("config.ini")

We'll now play around with the different methods you can use to access sections or individual keys.

For example, the snippet below shows you how to print all sections of a config.ini file:

print(config.sections())
Image 1 - Config file sections (image by author)
Image 1 - Config file sections (image by author)

If you want to access a single element, remember to access the section first. Python's regular dictionary notation will do:

print(config["dev"]["api_url"])
Image 2 - Accessing single config element (image by author)
Image 2 - Accessing single config element (image by author)

You can also use the get() method to access individual keys:

print(config.get("dev", "api_url"))
Image 3 - Accessing single config element (2) (image by author)
Image 3 - Accessing single config element (2) (image by author)

If you have any doubt that the key won't exist, or you want to be extra careful, specify the fallback argument when calling get():

print(config.get("dev", "not_exist", fallback=-1))
Image 4 - Accessing an element that doesn't exist (image by author)
Image 4 - Accessing an element that doesn't exist (image by author)

You can also iterate over an entire section and access its keys and values:

for key in config["dev"]:
    print(key)
Image 5 - Printing all keys in a single section (image by author)
Image 5 - Printing all keys in a single section (image by author)

But now comes the fun part. We've split the config.ini file into two sections - dev and prod. By default, that has no real-world meaning. What we can do is check which OS Python is running on. For example, if the code is running on macOS we can assume it's a development environment:

import platform

env = "dev" if platform.system().lower() == "darwin" else "prod"
print(env)
Image 6 - Accessing config sections dynamically (image by author)
Image 6 - Accessing config sections dynamically (image by author)

We can now use the env variable to access keys and values in the correct section. I'm running the code on a Mac, so the contents of the dev section get printed:

import platform

env = "dev" if platform.system().lower() == "darwin" else "prod"
for key in config[env]:
    print(f"{key} = {config[env][key]}")
Image 7 - Accessing config sections dynamically (2) (image by author)
Image 7 - Accessing config sections dynamically (2) (image by author)

And that's the basics of working with ini files in Python. Up next, you'll see how to use this type of configuration file when connecting to remote APIs.


How to Use ConfigParser Module in Python Apps

We'll now make a small Python script that connects to a remote REST API and downloads the data in JSON format. We already have URLs and paths in config.ini file. Create a new Python script and you're good to go.

The code snippet below makes a GET request to the API endpoint and saves the response locally. It also creates the directory structure if it doesn't exist:

import json
import platform
import pathlib
import configparser
import requests

env = "dev" if platform.system().lower() == "darwin" else "prod"
config = configparser.ConfigParser()
config.read("config.ini")


def get_users() -> dict:
    r = requests.get(config[env]["api_url"])
    return r.text


def save_users(users: dict) -> None:
    path = pathlib.Path(config[env]["save_dir"])
    if not path.exists():
        path.mkdir()

    with open(f"{config[env]['save_dir']}/{config[env]['save_file']}", "w") as f:
        json.dump(users, f)


if __name__ == "__main__":
    users = get_users()
    save_users(users=users)

Below you'll see the contents of the users_data directory:

Image 8 - Saved JSON file (image by author)
Image 8 - Saved JSON file (image by author)

And that's how you can integrate ini configuration files into your Python projects. Let's make a short recap next.


Summary of .ini Configuration Files with ConfigParser in Python

Hardcoding values in Python apps is always a terrible idea. It's easy to change a couple of things in a single file, but imagine you had hundreds of them. It's guaranteed you'll miss a couple of places and give headache to yourself and whoever you're working with.

Today you've learned how to work with ini configuration files in Python. The entire setup is as easy as they come, but it's not a be-all-end-all solution. For example, you can't use programming logic in .ini files. This shortcoming is easily addressed by using Python files for configuration. Stay tuned if you want to learn more about that.

Stay connected