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:
__init__.py
is loaded, then mail object is created.
Somewhere, create_app is called, then a new app object is created and uses the mail object previously created.
While processing the create_app , blueprints are imported. (Why do you need load blueprints within the app_context ?)
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.