Becoming minimally familiar with Flask is often an exhilarating experience. To Discover Flask is to rediscover instant gratification itself: all it takes is a mere few seconds for anybody deploy a Python application, alive and well on a development server. I believe the cliche that the hardest part of anything is getting started, so it's no surprise that Flaks can get the imagination brewing.

Indeed, there's no monolithic settings.py files in Flask (thank god). In fact, if you're to take Flask's documentation seriously, they might have you think that configuring Flask can happen any where in your code, any time you want:

from flask import Flask

app = Flask(__name__)
app.config['TESTING'] = True
app.py

The above works just fine. As the Flask docs promise, your app's config can be modified as you would a dictionary, and can be modified basically anywhere. But hold on a minute, young Padawan. With great power comes great responsibility. Do you really want your Flask app to start looking like this?:

from flask import Flask

app = Flask(__name__)

# Just configuring my app lmao
app.config['TESTING'] = True
app.config['DEBUG'] = True
app.config['ENV'] = development
app.config['SECRET_KEY'] = b'_5#y2L"F4Q8z\n\xec]/' # ohai storing my secret key in plain text in my app's main file LOL
app.config['DEBUG'] = False # come to think of it I want debug to be off now

# i should prolly start writing logic hahhh
@app.route('/')
def home():
    return render_template('/index.html')
    
    
app.config['SESSION_COOKIE_NAME'] = 'cookie_monstaaa' # oshit i forgot to set my cookie name
app.py

The point is, config variables belong in config files for a reason... especially sensitive variables. Today we'll be looking at some options and best practices for how to configure your Flask app, and which config variables you should know about.

Types of Config Files

Flask does us the courtesy of providing other configuration options to its, ehm, wonderful inline approach. These options includes configuration via object, envvar, or pyfile. We set the type of configuration file/type we want to use upfront when creating the app, like any of the below three demonstrations:

...

app.config.from_object('config.Config')      # Config from object
app.config.from_envvar('APP_CONFIG')         # Config from filepath in env
app.config.from_pyfile('application.cfg',
                       silent=True)          # Config from cfg file

Configure from Object

Configuring from an "object" is actually referring to creating a Python class, in which we can store all our variables in. This is our bread and butter, being the cleanest and most extensible way of handling Flask configurations. Let's see what a barebones Config class looks like. Something like the following is usually found in a config.py file in the root of your app:

"""Flask config class."""
import os


class Config:
    """Set Flask configuration vars."""

    # General Config
    TESTING = True
    DEBUG = True
    SECRET_KEY = b'_5#y2L"F4Q8z\n\xec]/'
    SESSION_COOKIE_NAME = 'my_cookie'
config.py

Simple and easy to understand! Each variable in our Config class is a reserved Flask config variable name. All we need to do is set these values, and Flask will know what we're talking about.

This is an improvement, but there's still a lot wrong... namely, we haven't solved the problem of our secret key hiding in plain sight! This is only the beginning of what kind of shitstorm would unfold if this file fell into the wrong hands, or is commited to version control. Here's a more realistic assortment of what a real app might hold in its config:

"""Flask config class."""
import os


class Config:
    """Set Flask configuration vars."""

    # General Config
    TESTING = True
    DEBUG = True
    SECRET_KEY = b'_5#y2L"F4Q8z\n\xec]/'
    SESSION_COOKIE_NAME = 'my_cookie'
    
    # Database
    SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://admin:pleasehackme@1.1.1.1:1738/imamoron'
    SQLALCHEMY_USERNAME='admin'
    SQLALCHEMY_PASSWORD='pleasehackme'
    SQLALCHEMY_DATABASE_NAME='imamoron'
    SQLALCHEMY_TABLE='passwords_table'
    SQLALCHEMY_DB_SCHEMA='public'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    
    # AWS
    AWS_SECRET_KEY = 'OBIVU5BIG$%&*IRTU#GV&^UHACKEDYGFTI7U5EGEWRG'
    AWS_KEY_ID = 'B&*D%F^&IYGUIFU'
    
    # My API
    API_ENDPOINT = 'http://unsecureendpoint.com/'
    API_ACCESS_TOKEN = 'HBV%^&UDFIUGFYGJHVIFUJ'
    API_CLIENT_ID = '3857463'

    # Test User Details
    TEST_USER = 'Johnny "My Real Name" Boi'
    TEST_PASSWORD = 'myactualpassword'
    TEST_SOCIAL_SECURITY_NUMBER = '420-69-1738'
    TEST_SECURITY_QUESTION = 'Main Street, USA'
config.py

I think you get the message. So what do we do? Add config.py to .gitignore and call it a day, right? WRONG.

The most obvious problem with omitting your config file from source control is that this will almost certainly go wrong at some point. Think you'll never forget to check your .gitignore file? Fine, but what about when that loser who sits across from you clones your repo and asks for that file? Will he know to omit it? Will YOU know to omit it when he changes the filename to config_v2_FINAL.py?

Security aside, there's another reason why hiding config.py is a bad idea. When the next person contributes to our codebase, how could they possibly know which variables live in your lone copy of the file? No matter how clan your code is, nobody will fully understand what's happening if they can't see what types of variables are driving the entire app. Here's my personal favorite solution this:

"""Flask config class."""
import os


class Config:
    """Set Flask configuration vars."""

    # General Config
    TESTING = os.environ.get('TESTING')
    DEBUG = os.environ.get('DEBUG')
    SECRET_KEY = os.environ.get('SECRET_KEY')
    SESSION_COOKIE_NAME = os.environ.get('SESSION_COOKIE_NAME')
config.py

Oh snap, environment variables! By storing our values in a .env file, we can reap the benefits 0f BOTH a config file and and .env file. The corresponding .env would look like this:

TESTING=True
DEBUG=True
SECRET_KEY=_5#y2L"F4Q8z\n\xec]/
SESSION_COOKIE_NAME=my_cookie

The added bonus here is now we can have multiple config classes, depending on whether we're in prod or dev:

"""Flask config class."""
import os


class Config:
    """Base config vars."""
    SECRET_KEY = os.environ.get('SECRET_KEY')
    SESSION_COOKIE_NAME = os.environ.get('SESSION_COOKIE_NAME')
    
    
class ProdConfig(Config):
    DEBUG = False
    TESTING = False
    DATABASE_URI = os.environ.get('PROD_DATABASE_URI')


class DevConfig(Config):
    DEBUG = True
    TESTING = True
    DATABASE_URI = os.environ.get('DEV_DATABASE_URI')
config.py

That's more like it!

Configure from Envvar

As mentioned previously, this is set via app.config.from_envvar('APP_CONFIG'). This option is absurdly simple: all we're saying is "I have a config file somewhere, which you can find in my .env file." We're still pointing to a file all the same, we've just taken another step in how its defined.

Configure from PyFile

Finally, there's this guy: app.config.from_pyfile('application.cfg',silent=True). I actually haven't ever configured a Flask app in this way, and truthfully don't care enough to go into detail about it :).

Configuration Variables

Enough all these files... what do Flask config vars actually mean? Once we configure common plugins alongside our default Flask app, things can get a bit nuts. Consider this a crash course, but is by no means required reading:

Default Configuration

I'll spare you the obscure variables, but here are the basics:

  • FLASK_ENV: The environment the app is running in, such as development or production. Setting the environment to development mode will automatically trigger other variables, such as setting DEBUG to True. Flask plugins similarly behave differently when this is true.
  • DEBUG: Extremely useful when developing! DEBUG mode triggers a number of things. Exceptions thrown by the app will print to console automatically, app crashes will result in a useful error screen, and your app will auto reload when changes are detected.
  • TESTING: Useful when running automated tests, this mode ensures exceptions are propagated rather than handled by the the app’s error handlers.
  • SECRET_KEY: A string which is used to encrypt sensitive information, such as passwords. If somebody has a hold of your app's secret key, the security of your app is compromised. Guard this collection of random characters with your life.
  • SERVER_NAME: If your app is to be served at a custom domain, this is specified 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 specifying whether or not 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 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 which store user information per session. While Flask supports storing information 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 which 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 certain 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 configured a shit ton so far, it seems. And yet, there are still bits and pieces flying around Flask which inevitably need to be configured inline. Consider app = Flask(__name__, instance_relative_config=False): indeed, we've set a configuration value when creating our app! But wasn't the point to avoid this?! Well, yes, but check out what we configured: the location of config.py!

I think we've all had enough configuring for one day. Hopefully this cleared something up for somebody out there!