I’ve written an article about handling flask configurations. In that article, I discussed 2 ways in which we can handle application configuration. However, there is a third method of handling flask configurations that I did not discuss: using environment variables. In this article, I’m going to expound on this method and provide code examples.
Environment variable basics
Environment variables are dynamically-named values that are exposed to running processes on a machine. These values can be accessed by processes in order to obtain certain information.
In trying to understand environment variables, it is important to think of them in comparison to program variables that you’ll have in your source code.
In your source code, you’ll have variables that you use throughout the program. These variables can be local or global and are only accessible within the context of the program. Each of these types having a different scope (global variables can typically be used across the entire program/module while local variables are confined to a particular function/method).
However, program variables are private to the process that defines them, unless explicitly exposed. Environment variables, on the other hand, are a step up from program global variables. These are system-wide variables that can be used by any process running within the system.
For example, if you’re running a program on a macOS machine, you can access the ‘$HOME’ environment variable. This gives you the current user’s home directory and can be accessed by any process running on the machine.
Different machines have different default environment variables that ship with the system. However, most environments will allow you to set your own environment variables for use within processes that run within that environment.
Just like program variables are confined to the process that declares them, environment variables are confined to the environment in which they’re defined. For example, if I was to set an environment variable “football_team” on my machine, that environment variable would be accessible by all processes running on my machine but not on another machine.
You can read more on environment variables here.
Why use them?
Now that we’ve discussed what environment variables are, let’s discuss why they’re useful. As we’ve stated earlier, they are system-wide and are confined to the system. This is important when it comes to dealing with flask applications that run on multiple environments.
If we have a production and development environment, we might have different values for our secret_key, database URI, email server, etc. Basically, we want to separate those application instances and have them use different values for the same variables with minimal code changes.
Environment variables allow us to do this by allowing us to set the values for these variables at the system level. We can then simply access them in our application.
Another benefit of environment variables is that they allow us to keep values hidden. If you want to make your project open-source or you simply have a lot of developers working on a project and you want to make sure sensitive values are protected from being seen by everyone, environment variables are key.
This is because setting the value on the machine keeps it outside of your source code. Whoever has access to the source code, only has access to the code that’s accessing the variable, and not the value itself.
Do note that, when taking this approach, you will have to set the environment variable in all the different environments that the application will run on. Otherwise, you’ll have to provide an alternative value in case the environment variable is not found. I will explain this in more detail later on in the article.
Flask configuration recap
Before, we get into how to implement environment variables in our configuration setup, let's quickly recap on 2 of the recommended methods of handling application configuration that we discussed previously.
The first method uses configuration files to set app configurations. In this method, we have a configuration file for each environment. The configuration file will have our configuration variables as displayed below:
MONGO_URI = "environment_mongo_uri" SECRET_KEY = "environment_secret_key"
The code that loads the configuration file into the app:
In this method, we define classes that represent our configurations. We will have a base class that has shared configuration settings between all environments, and then create a different class of configurations for each environment that the application will run on. Here is an example:
class BaseConfig(object): DEBUG = False MONGO_URI = "mongo_uri" class DevelopmentConfig(BaseConfig): DEBUG = True MONGO_URI = "development_mongo_uri" class TestingConfig(BaseConfig): DEBUG = True MONGO_URI = "testing_mongo_uri" class ProductionConfig(BaseConfig): MONGO_URI = "production_mongo_uri"
We can then load the appropriate configurations for an environment using the following code, where ‘configmodule’ is the python module containing the configuration classes:
Implementing environment variables
There are 2 ways that we can implement environment variables. These somewhat depend on how you approach environment variables. We can use both of the methods above in combination with environment variables in order to achieve the desired result.
Environment variables with configuration files
Let’s start with the easier method first. We can use environment variables with configuration files. To do this, create a configuration file just like you normally would, but instead of having the configuration file in your project folder/version control, store it in another location. Then create an environment variable that contains a path pointing to the configuration file. Next, load the configuration file in your app using the following code:
In this example,
APP_CONFIG is the environment variable containing the path to the configuration file. If you have default configurations that you need to override, make sure they are loaded before this line.
Environment variables with configuration objects
This is my favourite way of dealing with environment variables. If we’re using environment objects, we have more granular control over which configuration variables we set as environment variables and which ones we can leave in our code. We can still achieve this using configuration files but using objects allows us to keep our configurations a lot more organised.
So using the example above, We will add a
SECRET_KEY to our configuration. Since we do not want this value to be visible to everyone looking at our code, we will set it in our environment, and then load our configuration object. The configuration objects will then look like this:
import os class BaseConfig(object): DEBUG = False MONGO_URI = "mongo_uri" class DevelopmentConfig(BaseConfig): DEBUG = True MONGO_URI = "development_mongo_uri" SECRET_KEY = os.getenv('PROJECT_SECRET_KEY', 'DefaultKey') class TestingConfig(BaseConfig): DEBUG = True MONGO_URI = "testing_mongo_uri" SECRET_KEY = os.environ.get('PROJECT_SECRET_KEY') or "TestingKey" class ProductionConfig(BaseConfig): MONGO_URI = "production_mongo_uri" SECRET_KEY = os.environ['PROJECT_SECRET_KEY']
Here we use the built-in python library
os to help us. You might have noticed that we have used 3 different methods of setting the secret key using environment variables.
os.getenv, takes 2 arguments, the environment variable, and a default value. If the environment variable is not found, the default value is used.
os.environ.get takes one argument, the environment variable name. If the variable isn’t found, it returns None. However, we don’t want None as the secret key, so we use the or keyword to make sure that we default to “TestingKey” if the environment variable is not found.
Lastly, we have
os.environ which allows us to access environment variables as a dictionary. Be careful with this as it throws a
KeyError if the variable is not found. So you want to make sure the environment variable is set, or wrap the statement in a try/except block.
You can now load the appropriate configuration object for your environment.
In this article, I’ve outlined the basics of environment variables and how they can be incorporated into a flask application’s configuration. These are a very powerful tool in your programming arsenal as they open up new possibilities in your code implementations if used correctly.