The software industry has seen no shortage of newly introduced web frameworks. That said, for old-school Pythonistas with a background of wrestling Django monoliths, discovering Flask is to rediscover instant gratification itself: all it takes is a mere few seconds for anybody to deploy a Python application, alive and well on a development server. I believe the cliche is that the most challenging part of anything is getting started, so it's no surprise that Flaks can get the imagination brewing.

Configuring apps isn't the sexiest topic to cover in hot new frameworks, which is precisely why most newcomers skip over this aspect of Flask development entirely. This level of short-term procrastination seems harmless to the untrained eye. From my perspective, it's like watching a generation of grown professionals postponing their child's potty training by one more day: both cases ultimately end with grown adults shitting their pants.

Every web app must be configured with database URIs and secret keys to enable critical functionality. Unlike Django, there's no monolithic settings.py file in Flask (thank god). Instead, we can choose to configure Flask via several methods. Today we're covering two topics in Flask configuration: best practices in configuration structure and configuration settings.

What Not To Do: Inline Configuration

The fastest, dirtiest, and most dangerous method for configuring Flask is by directly setting config vars in source code using plain text. Amongst tutorials, it also seems to be the most common. Stop me if you've seen something like this before:

from flask import Flask


app = Flask(__name__)
app.config['FLASK_ENV'] = 'development'
app.py

From an author's perspective (writing tutorials for strangers), it's convenient to pretend that setting config values this way is acceptable. Surely nobody clicking into "How To Do This Sensational Thing in Flask" tutorials cares about learning the best software development practices, which encourages many bad behaviors and leaves many questions unanswered. For instance, why would we wait until after we create the Flask app object to set something as crucial as FLASK_ENV? Isn't the point of configuration to inform our app on how to function before it starts... functioning?

As the Flask docs promise, your app's config can be modified as we did above and can be modified/changed at any time. Let's set aside the horrendous readability this creates down the line. We'll even turn a blind eye to the unimaginable shit show we create by mutating configs while our app runs. To make matters worse, it encourages committing sensitive values, such as secret keys in the middle of your codebase:

"""Initialize Flask application."""
from flask import Flask


# Flask app creation
app = Flask(__name__)


# BAD: Inline configuration with hardcoded values
app.config['ENVIRONMENT'] = "development"
app.config['FLASK_APP'] = "my-app"
app.config['FLASK_DEBUG'] = True
app.config['SECRET_KEY'] = "GDtfD^&$%@^8tgYjD"

# BAD: Changing config values in code
app.config['ENVIRONMENT'] = "development"


# Actual app logic 
@app.route('/')
def home():
    """Render the home page."""
    return render_template("/index.jinja2")
app.py

We could update these settings in bulk instead, which is a slightly prettier way of executing the same horrible configuration pattern:

...

app.config.update(
    FLASK_APP="my-app",
    FLASK_DEBUG=True,
    SECRET_KEY="GDtfD^&$%@^8tgYjD",
)

...
app.py

Config variables belong in config files for a reason. They're an organized set of instructions defined before our app is live. Moreover, it's much easier to avoid compromising sensitive secrets such as AWS or database credentials when we manage these things in one place.

Let's explore our better options. We have two immediate problems to solve:

  1. Separation of config logic & business logic.
  2. Avoiding hardcoded values in code wherever possible.

Configuration from a .py File

The simplest (not to be confused with "best") way to configure a Flask app is by defining config values as global variables in config.py:

"""Flask App configuration."""

# General Config
ENVIRONMENT = "development"
FLASK_APP = "my-app"
FLASK_DEBUG = True
SECRET_KEY = "GDtfD^&$%@^8tgYjD"
config.py

Our new app.py finds this file from the project root by specifying the location of the config:

"""Initialize Flask application."""
from flask import Flask


app = Flask(__name__)
app.config.from_pyfile("config.py")
app.py

This allows us to avoid the messy code we saw earlier by isolating our configuration to a file separate from our app logic. Flask immediately recognizes these variables as Flask-specific and implements them without further effort. This is a big win regarding encapsulation, but we still haven't solved the issue of exposing stuff like SECRET_KEY in a file that might make its way onto a public Github repo by accident someday. A standard solution is to pull these values from environment variables.

Defining Config Values as Environment Variables

An "environment variable" is a value stored in the system memory of the device running your app. Environment variables can be temporarily created via the terminal like so:

export SECRET_KEY='GDtfDCFYjD'
Set an environment variable

SECRET_KEY will exist as long as you keep your terminal open.  You can retrieve variables like these on your local machine like so:

$ echo $SECRET_KEY
>> 'GDtfDCFYjD'
Check the value assigned to an environment variable

It would be annoying to set these variables every time we open our terminal, so we can set environment variables in a local file called .env instead and grab those variables using a Python library like python-dotenv:

pip install python-dotenv
Installing a .env reader for fetching config values
"""Flask APP configuration."""
from os import environ, path
from dotenv import load_dotenv


# Specificy a `.env` file containing key/value config values
basedir = path.abspath(path.dirname(__file__))
load_dotenv(path.join(basedir, ".env"))


# General Config
ENVIRONMENT = environ.get("ENVIRONMENT")
FLASK_APP = environ.get("FLASK_APP")
FLASK_DEBUG = environ.get("FLASK_DEBUG")
SECRET_KEY = environ.get("SECRET_KEY")
config.py

Our app is now both significantly cleaner and more secure as long as sensitive values live in .env:

SECRET_KEY="GDtfDCFYjD"
.env

Configuring Flask From Class Objects

We can take our configuration a step further by setting different configuration values depending on which environment we're running in (production, development, etc). We accomplish this by splitting configurations into Python classes in config.py:

"""Initialize Flask application."""
from flask import Flask


app = Flask(__name__)
app.config.from_object('config.Config')
app.py

Here's what config.py looks like now:

"""Flask App configuration."""
from os import environ, path
from dotenv import load_dotenv

# Specificy a `.env` file containing key/value config values
basedir = path.abspath(path.dirname(__file__))
load_dotenv(path.join(basedir, '.env'))


class Config:
    """Set Flask config variables."""

    # General Config
    ENVIRONMENT = environ.get("ENVIRONMENT")
    FLASK_APP = environ.get("FLASK_APP")
    FLASK_DEBUG = environ.get("FLASK_DEBUG")
    SECRET_KEY = environ.get("SECRET_KEY")
    STATIC_FOLDER = 'static'
    TEMPLATES_FOLDER = 'templates'
    
    # Database
    SQLALCHEMY_DATABASE_URI = environ.get('SQLALCHEMY_DATABASE_URI')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    
    # AWS Secrets
    AWS_SECRET_KEY = environ.get('AWS_SECRET_KEY')
    AWS_KEY_ID = environ.get('AWS_KEY_ID')
config.py

Config Classes per Environment

I'm not crazy about the paradigm below. I believe the term "environment variable" already implies values returned should be specific to the current environment, thus rendering separate configs unnecessary. For the sake of covering our bases, be aware that there are some madmen on the loose out there doing things like this:

"""Flask configuration."""
from os import environ, path
from dotenv import load_dotenv


basedir = path.abspath(path.dirname(__file__))
load_dotenv(path.join(basedir, '.env'))


class Config:
    """Base config."""
    SECRET_KEY = environ.get('SECRET_KEY')
    SESSION_COOKIE_NAME = environ.get('SESSION_COOKIE_NAME')
    STATIC_FOLDER = 'static'
    TEMPLATES_FOLDER = 'templates'
    
    
class ProdConfig(Config):
    """Production config."""
    FLASK_ENV = "production"
    FLASK_DEBUG = False
    DATABASE_URI = environ.get('PROD_DATABASE_URI')


class DevConfig(Config):
    """Development config."""
    FLASK_ENV = "development"
    FLASK_DEBUG = True
    DATABASE_URI = environ.get('DEV_DATABASE_URI')
config.py

Instead of a one-size-fits-all config, we can now swap configurations based on Flask's environment. ProdConfig and DevConfig contain values specific to production and development, respectively. Both of these classes extend a base class Config which contains values intended to be shared by both. Swapping between configs is now this easy:

# Using a production configuration
app.config.from_object('config.ProdConfig')

# Using a development configuration
app.config.from_object('config.DevConfig')
app.py

I want to stress, however, that I believe this paradigm is dumb, to the point where an explanation is not warranted.

Configuration Variables

Now, what do all these variables mean anyway? We've polished our app to have a beautiful config setup, but what do SECRET_KEY or SESSION_COOKIE_NAME even mean anyway? We won't go down the rabbit hole of exploring every obscure setting in Flask, but I'll give you a crash course on the good parts:

  • FLASK_DEBUG: The successor of now-deprecated FLASK_ENV and DEBUG configs. Flask Debug should only be used in development environments to provide additional logging for debug purposes.
  • SECRET_KEY: Flask "secret keys" are random strings used to encrypt sensitive user data, such as passwords. Encrypting data in Flask depends on the randomness of this string, which means decrypting the same data is as simple as getting a hold of this string's value. Guard your secret key with your life; ideally, even you shouldn't know the value of this variable.
  • SERVER_NAME: If you intend your app to be reachable on a custom domain, we specify the app's domain name here.

Static Asset Configuration

Configuration for serving static assets via the Flask-Assets library:

  • ASSETS_DEBUG: Enables/disables a debugger specifically for static assets.
  • COMPRESSOR_DEBUG: Enables/disables a debugger for asset compressors, such as LESS or SASS.
  • FLASK_ASSETS_USE_S3: Boolean value specifying whether or not assets should be served from S3.
  • FLASK_ASSETS_USE_CDN: Boolean value specifies whether assets should be served from a CDN.

Flask-SQLAlchemy

Flask-SQLAlchemy is pretty much the choice for handling database connections in Flask:

  • SQLALCHEMY_DATABASE_URI: The connection string of your app's database.
  • SQLALCHEMY_ECHO: Prints database-related actions to the console for debugging purposes.
  • SQLALCHEMY_ENGINE_OPTIONS: Additional options to be passed to the SQLAlchemy engine, which holds your app's database connection.

Flask-Session

Flask-Session is excellent for apps that store user information per session. While Flask supports storing data in cookies by default, Flask-Session builds on this by adding functionality and additional methods for where to store session information:

  • SESSION_TYPE: Session information can be handled via Redis, Memcached, filesystem, MongoDB, or SQLAlchemy.
  • SESSION_PERMANENT: A True/False value that states whether or not user sessions should last forever.
  • SESSION_KEY_PREFIX: Modifies the key names in session key/value pairs to always have a particular prefix.
  • SESSION_REDIS: URI of a Redis instance to store session information.
  • SESSION_MEMCACHED: URI of a Memcached client to store session information.
  • SESSION_MONGODB: URI of a MongoDB database to store session information.
  • SESSION_SQLALCHEMY: URI of a database to store session information.

Last Thoughts

We've spent way more time on the topic of configuring Flask than anybody could enjoy. Instead of making a dry technical post longer, why not learn from an example by cruising our collection of countless Flask tutorials? We've pumped out at least a dozen Flask projects - feel free to find an example Flask project that meets your needs and take it from there.

Hackers and Slackers
Software Engineering, Data Science, & Data Engineering tutorials. - Hackers and Slackers
Our Hackersandslackers Github org contains plenty of Flask tutorials