Configuring Environment Variables with Starlette

October 17, 2021

Overview

When working with applications and services it is often necessary to configure various settings in order for the application to operate. This can be non-sensitive information like logging or directory paths, or sensitive information like passwords, secrets and database connections.

It is considered good practice to separate configuration settings from code. This may be done by storing configuration settings in environment variables on the OS server where the application is deployed. By doing so, you avoid hard-coding and commiting sensitive information to source control and provide an easy mechansim to define configuration settings at deployment.

The .env File

The .env (dot env) file is a simple text file that contains key-value pairs used to store and load environment variables. The key is the name of the environment variable and the value is the value of that variable. The .env file is stored at the root of your application and must not be committed to source control (add it to your .gitignore file for peace of mind).

An example .env file may look like this:

USERNAME=luke
PASSWORD=qwerty
SECRET_KEY=123456789

Starlette Configuration

Starlette is a lightweight ASGI framework used for building high performance asyncio services. Starlette has a useful built-in Config class used to load environment variables into an application.

The Config class allows you load configuration variables from environment variables, a .env file and local defaults (with precedence given to this order if the same keys exist across the different sources).

As an example, we can initialise a Config object and then use it to load module-level variables as follows:

from starlette.config import Config
from starlette.datastructures import Secret

config = Config(".env")

USERNAME = config("USERNAME", cast=str, default="luke")
PASSWORD = config("PASSWORD", cast=Secret)
SECRET_KEY = config("SECRET_KEY", cast=Secret)

Starlette allows you to cast the variables being loaded to conform to specific data types. Furthermore, it provides custom data structures including a Secret class (for obscuring sensitive information) and a CommaSeparatedStrings class (for loading comma-separated values from the .env file).

When we want to use these configuration variables, we can import the config module and use the module-level variables that have been defined:

from app import config

application = some_application(
    username=config.USERNAME,
    password=config.PASSWORD,
    secret_key=config.SECRET_KEY
)

If we have tests that use these configuration variables, we can overwrite them by setting custom environment variables using Starlette's environ instance. This helps prevent production settings being used in a testing environment.

from starlette.config import environ

environ["USERNAME"] = "test_username"
environ["PASSWORD"] = "test_password"
environ["SECRET_KEY"] = "test_secret_key"