Ambassador Kosh
Ambassador Kosh

Reputation: 511

Flask app secret key is not loading with Gunicorn

When I try to run my Flask using gunicorn -w 3 wsgi:app, and visit the page via the ip address, then I receive the following error: RuntimeError: A secret key is required to use CSRF. in the terminal.

I am not sure what is causing this issue because: I have set a secret key in .env and I have confirmation that it is being loaded by Flask because when I run the app using flask run --host=0.0.0.0, then the codeprint(app.config['SECRET_KEY']) prints the secret key to the terminal.
I imagine this is something to do with Gunicorn needing a different code for it to load the secret key.

My Flask app is structured like so:

-denise
--configmodule.py
--__init__.py
--site.db
--models.py
--main
--static
--template
-migrations
-venv
-wsgi.py
-.env

I don't have much experience setting up servers, here are the code from the relevant files:

__init__.py file:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from denise.configmodule import Config
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
from flask_mail import Mail
from flask_migrate import Migrate
from flask_wtf.csrf import CSRFProtect

db = SQLAlchemy()
bcrypt = Bcrypt()
login_manager = LoginManager()
mail = Mail()
csrf = CSRFProtect()

def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)
    csrf.init_app(app)
    migrate = Migrate(app,db)
    db.init_app(app)
    bcrypt.init_app(app)
    login_manager.init_app(app)
    mail.init_app(app)
    print('SECRET_KEY')
    print(app.config['SECRET_KEY'])
    print('SECRET_KEY')

    #with app.app_context()
    from .main.routes import main

    app.register_blueprint(main)
    return app                                

configmodule.py file:

from os import environ, path
from dotenv import load_dotenv

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

class Config(object):
    DEBUG=True
    SECRET_KEY=environ.get('SECRET_KEY')
    SQLALCHEMY_DATABASE_URI='sqlite:///site.db'
    ENV='development'
    MAIL_SERVER = 'smtp.googlemail.com'
    MAIL_PORT = 587
    MAIL_USE_TLS = True
    MAIL_USERNAME = environ.get('EMAIL_USER')
    MAIL_PASSWORD = environ.get('EMAIL_PASS')
    MAIL_DEFAULT_SENDER = environ.get('EMAIL_DEFAULT_SENDER')
    MAIL_MAX_EMAILS = 5
    RECAPTCHA_PUBLIC_KEY = environ.get('RECAPTCHA_PUBLIC')
    RECAPTCHA_PRIVATE_KEY = environ.get('RECAPTCHA_PRIVATE')                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       

Wsgi.py file:


from denise import create_app
from dotenv import load_dotenv

load_dotenv('.env')

app = create_app()

if __name__ == "__main__":
    app.run()

.env file:

SECRET_KEY='hiiii17011b97b7ed4aeb9ae7f75a0b66a006c8efd4ab0759e5d2'
EMAIL_USER='[email protected]'
EMAIL_DEFAULT_SENDER='[email protected]'
EMAIL_PASS='password'
RECAPTCHA_PUBLIC='randomstringofletter'
RECAPTCHA_PRIVATE='lalalalalal'                                                                  

I have tried a few tutorials on deploying Flask apps to production by Corey Schafer and Pretty Printed. I guess I have missed something somewhere.

Upvotes: 4

Views: 1627

Answers (2)

Edo Akse
Edo Akse

Reputation: 4401

Good old spamming of print statements to the rescue.

Note, I used gunicorn -w 1 -t 1 wsgi:app to ensure single worker & thread, as otherwise the console is spammed with print statements from multple sources.

Importing & loading order is the likely culprit. There's also an issue with the statement load_dotenv(path.join(basedir, '.env')) in configmodule.py. it looks for the .env file in the directory denise and fails silently.

I've made a minimally reproducable example, see below.

The fix is to either:

  • do the load_dotenv('.env') in wsgi.py before importing anything from denise
  • change the way you load inside configmodule.py to load_dotenv(path.join(basedir, '../.env')), but since you use the Application Factory Pattern structure I'm not sure if this is advisable though. This can lead to undesired behavior in the future.

configmodule.py

from os import environ, path
from dotenv import load_dotenv, dotenv_values
from pprint import pprint


print('--------------------------------------------------------------------------------------------------------------------------------')
print('configmodule')
print('--------------------------------------------------------------------------------------------------------------------------------')


basedir = path.abspath(path.dirname(__file__))
load_dotenv(path.join(basedir, '.env'))
try:
    with open(path.join(basedir, '.env')) as infile:
        for line in infile:
            print(line)
except:
    print('=========================== FILENOTFOUND')


class Config(object):
    print('--------------------------------------------------------------------------------------------------------------------------------')
    print('configmodule:Config')
    print('--------------------------------------------------------------------------------------------------------------------------------')
    pprint(dotenv_values(path.join(basedir, '.env')))
    print('--------------------------------------------------------------------------------------------------------------------------------')
    pprint(environ)
    DEBUG=True
    SECRET_KEY=environ.get('SECRET_KEY')
    SQLALCHEMY_DATABASE_URI='sqlite:///site.db'
    ENV='development'
    MAIL_SERVER = 'smtp.googlemail.com'
    MAIL_PORT = 587
    MAIL_USE_TLS = True
    MAIL_USERNAME = environ.get('EMAIL_USER')
    MAIL_PASSWORD = environ.get('EMAIL_PASS')
    MAIL_DEFAULT_SENDER = environ.get('EMAIL_DEFAULT_SENDER')
    MAIL_MAX_EMAILS = 5
    RECAPTCHA_PUBLIC_KEY = environ.get('RECAPTCHA_PUBLIC')
    RECAPTCHA_PRIVATE_KEY = environ.get('RECAPTCHA_PRIVATE')

wsgi.py

from os import environ
from denise import create_app
from dotenv import load_dotenv, dotenv_values


print('--------------------------------------------------------------------------------------------------------------------------------')
print('WSGI')
print('--------------------------------------------------------------------------------------------------------------------------------')
load_dotenv('.env')
print(dotenv_values('.env'))
print('--------------------------------------------------------------------------------------------------------------------------------')
with open('.env') as infile:
    print(infile.readlines())
print('--------------------------------------------------------------------------------------------------------------------------------')
print(environ)
print('--------------------------------------------------------------------------------------------------------------------------------')

app = create_app()

if __name__ == "__main__":
    app.run()

I would suggest to modify wsgi.py to prevent future issues, as stated above, although this might violate PEP8 standards.

The fixed wsgi.py

from dotenv import load_dotenv
load_dotenv('.env')

from denise import create_app


app = create_app()

if __name__ == "__main__":
    app.run()

Upvotes: 3

westandskif
westandskif

Reputation: 982

(writing from phone)

Have a look at your wsgi file:

  1. first you import create_app, it goes to your __init__ file
  2. __init__ file imports Config, which is not lazily initialized, but on import (i’d change this, but it’s not an issue)
  3. in config file you take dirname of __file__, which is denise, then you join it with .env, while there is no env file in denise directory

Regarding running flask run to debug, I don’t use flask, but it may just not use wsgi file.

Upvotes: 1

Related Questions