简体   繁体   中英

Flask application factory pattern with Flask-Mail

I have a simple conceptual question about the Flask application factory pattern.

I'm trying to use Flask-Mail across different files. My __init__.py file is:

#
# __init__.py
#
from flask import Flask
from flask_pymongo import PyMongo
from flask_mail import Mail
from flask_login import LoginManager

mail = Mail()
mongo = PyMongo()
login_manager = LoginManager()

def create_app():
    app = Flask(__name__, instance_relative_config=False)
    app.config.from_object('config.DevConfig')

    mail.init_app(app)
    login_manager.init_app(app)
    mongo.init_app(app, retryWrites = False)

    with app.app_context():
        from .views import bp
        app.register_blueprint(views.bp)
    return app

And the other file:

#
# views.py
#
from flask import Blueprint
from flask import current_app as app
from flask_mail import Message

from app import mongo, mail, login_manager

bp = Blueprint('bp', __name__, template_folder='templates', static_folder='static')

@bp.route('/')
def index():
    msg = Message("Success", recipients=[ email ])
    with open('template.html', 'r') as fd:
        msg.html = fd.read()
    mail.send(msg)

Though I set MAIL_DEFAULT_SENDER in my config file, I'm getting the error that there is no default sender specified when hitting the mail.send(msg) line in views.py . After checking the mail object, I saw that it had no config variables set.

Per this tutorial , it seemed that I'd need to manually set current_app.config['MAIL_DEFAULT_SENDER'] whenever using the mail object under this pattern, and would need to write an additional with app.app_context(): block around the mail object such that it was instantiated with the proper config variables.

This seems like a lot of extra work, so is there another way to directly get the mail object that was initialized in create_app with all the proper config variables set?

Appreciate the help with this!

EDIT:

Created extensions.py :

from flask_pymongo import PyMongo
from flask_mail import Mail
from flask_login import LoginManager

mail = Mail()
mongo = PyMongo()
login_manager = LoginManager()

And modified __init__.py to be:

from flask import Flask

def create_app():
    app = Flask(__name__, instance_relative_config=False)
    app.config.from_object('config.DevConfig')

    from app.extensions import mail, login_manager, mongo
    mail.init_app(app)
    login_manager.init_app(app)
    mongo.init_app(app, retryWrites = False)

    with app.app_context():
        from .views import bp
        app.register_blueprint(views.bp)
    return app

And for views.py , I have:

from flask import Blueprint
from flask import current_app
from flask_mail import Message

from app.extensions import mongo, mail, login_manager

bp = Blueprint('bp', __name__, template_folder='templates', static_folder='static')

@bp.route('/')
def index():
    msg = Message("Success", recipients=[ email ])
    with open('template.html', 'r') as fd:
        msg.html = fd.read()
    mail.send(msg)

It is an import problem. Please read the official documentation about Application Factories & Extensions .

Explanations:

  1. __init__.py is loaded, then mail object is created.

  2. Somewhere, create_app is called, then a new app object is created and uses the mail object previously created.

  3. While processing the create_app , blueprints are imported. (Why do you need load blueprints within the app_context ?)

  4. While blueprint is imported, it imports from app [...], mail . Imports are not shared among modules (in this case for sure). So __init__.py is executed again while being imported and created a new mail object that is not configured. (If you use a debugger you will see that there are 2 mail objects.)

In the end, you have 2 mail object: one is configured and one is not .

How to handle that:

As written in the documentation:

It's preferable to create your extensions and app factories so that the extension object does not initially get bound to the application.

And they give the following example:

You should not do:

 def create_app(config_filename): app = Flask(__name__) app.config.from_pyfile(config_filename) db = SQLAlchemy(app)

But, rather, in model.py (or equivalent):

 db = SQLAlchemy()

and in your application.py (or equivalent):

 def create_app(config_filename): app = Flask(__name__) app.config.from_pyfile(config_filename) from yourapplication.model import db db.init_app(app)

Finally, you should create your mail object in a different file and import it your __init__.py . If you only use the mail object in this blueprint, it could be declared directly in this blueprint.

You can find a full example: Flaskr (Official example) that uses a DB extension as you use Flask-Mail.

Why is it so confusing? Most of the examples you can find on Google, do not use application factories and are always in one file. In their case, you will declare everything in the same file. In your case, you will share objects among modules, so they should be accessible correctly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM