Using environmental variables in python with direnv and datek-app-utils


Foreword

This is an opinionated article. When I say something is better or is not correct, I mean it only in my opinion, not scientifically.

I’m sure you already had some business with environmental variables, especially if you’re following the III. principle of a 12factor application.


I’m also sure you’ve read a lot of tutorials about how to deal with env vars, and unfortunately most of them recommend using the python-dotenv library.

Just a couple examples:


This article is offering a better, correct way of reading environmental variables.

Why using python-dotenv isn’t correct according to the 12factor ?

According to the the III. principle,


The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard.”


Let’s talk about the following example:


from os import getenv
from dotenv import load_dotenv


def main():
    load_dotenv()  # <--- This line reads a file!
    database_url = getenv("APP_DATABASE_URL")
    ...

As I pointed out, load_dotenv() line is actually reading a file! So the config originally comes from a file, not from the environment! With this effort you could read the config directly from .yml or .json or .toml files, right?


The whole point of 12factor is that the application should not read the config from files, which means when the application starts, the config should already be found in the environment!

The correct way

Thankfully, there is a great tool to save us: direnv !


With direnv you can create a .envrc file, which will be run when you’re entering the working directory in your terminal.


A very minimalistic, one-liner .envrc file:

dotenv

With this one line, the direnv hook will read your .env file and it will automatically export all environmental variables into your shell.


With this, your app won’t break the III. principle anymore:

from os import getenv


def main():
    database_url = getenv("APP_DATABASE_URL")
    ...

Using datek-app-utils

With datek-app-utils you can:


from datek_app_utils.env_config.base import BaseConfig, validate_config

class Config(BaseConfig):
    APP_DATABASE_URL: str


def main():
    validate_config(Config)

    database_url = Config.APP_DATABASE_URL
    ...

I hope you liked this post and I hope the tools I showed here will help you writing better software and improve your developer experience.

Special thanks to