5

I'm building an app using Flask with an application factory design pattern. I want to use Flask-Mail in one of my views which gets registered to the app via a Blueprint.

I saw from this Flask - cannot use Flask and Flask-mail instances from other files question that you're supposed to instantiate the Mail() object outside of the create_app() function like so:

from flask_mail import Mail

mail = Mail()

def create_app(config_lvl):
    # stuff
    mail.init_app(app)
    # more stuff
    return app

Then you can import the mail object into your view files and access it from there.

However for this to work you need to make sure the mail object in __init__.py for your app is instantiated before you import the Blueprint that contains the view which uses the mail object. If you don't do this you get an import error.

To me this feels hacky and, although Flask oftentimes seems quite happy with this sort thing, I was hoping that the application factory design pattern would minimise this sort of import jiggery-pokery.

My solution was to just attach the mail client to the app object so that it can be accessed from anywhere else using current_app.mail like so:

## __init.py __ ##
from flask_mail import Mail

def create_app(config_lvl):
    # stuff
    app.mail = Mail(app)
    # more stuff
    return app

## views.py ##
from flask_mail import Message
from flask import current_app 

def send_email(to, subject, template):
    msg = Message(
        subject,
        recipients=[to],
        html=template,
     sender=current_app.config['MAIL_DEFAULT_SENDER']
    )
    current_app.mail.send(msg)

This feels like a simpler way of accessing the mail client throughout the app, rather than faffing around making sure the order in which you import various things is correct.

What I want is for someone to tell my why this is not a good idea.

user2993689
  • 283
  • 3
  • 11

1 Answers1

8

For me the correct way to do it is to define an extension file that contains instance of flask-extensions like Mail. In extensions.py

from flask-mail import Mail
mail = Mail()

and from your create_app function call your mail.

from extension import mail

def create_app(config_lvl):
    mail.init_app(app)
    return app

You can call after that your mail variable from extensions.py in all your project.

bmagnette
  • 70
  • 2
  • 10
  • Thank you for your response! Your solution seems sensible enough to me as well. It seems this is just a case of preference, then? – user2993689 Aug 03 '19 at 12:35
  • I think that your method as some limits, as example how can you use your mail outside the flask's context ? I'm not sure about it but I think that it will not work. In the code that I showed above I'm sure it works. :) is it answering your question ? – bmagnette Aug 05 '19 at 10:20
  • You're right, my way only works if I can access the current_app context. I've actually switched to your method now as I've added some new extensions to my app (flask-migrate, flask-login) and having that all in an extensions.py feels like a neat way of doing things and works well witht the app factory design pattern. Will accept your answer now, thanks! – user2993689 Aug 06 '19 at 14:25