4

I have built a flask app that I have been starting from an if __name__ == '__main__': block, as I saw in a tutorial. When the time came to get the app to launch from wsgi for production use, I had to remove the port and host options from app.run(), and make changes to the structure of the launching code that I am not too sure about. I am now adding test cases, which adds more ways to launch and access the app (with app.test_client(), with app.test_request_context(), and who knows what else.) What is the right / recommended way to structure the code that creates and launches the application, so that it behaves correctly (and consistently) when launched stand-alone, from wsgi, and for testing?

My current structure is as follows:

def create_app():
    """
    Create and initialize our app. Does not call its run() method
    """
    app = Flask(__name__)
    some_initialization(app, "config_file.json")

    return app

app = create_app()

...
# Services decorated with @app.route(...)
...

if __name__ == "__main__":
    # The options break wsgi, I had to use `run()`
    app.run(host="0.0.0.0", port=5555)

To be clear, I have already gotten wsgi and tests to work, so this question is not about how to do that; it is about the recommended way to organize the state-creating steps so that the result behaves as a module, the app object can be created as many times as necessary, service parameters like port and server can be set, etc. What should my code outline actually look like?

In addition to the launch flag issue, the current code creates an app object (once) as a side effect of importing; I could create more with create_app() but from mycode import app will retrieve the shared object... and I wonder about all those decorators that decorated the original object.

I have looked at the documentation, but the examples are simplified, and the full docs present so many alternative scenarios that I cannot figure out the code structure that the creators of Flask envisioned. I expect this is a simple and it must have a well-supported code pattern; so what is it?

alexis
  • 48,685
  • 16
  • 101
  • 161
  • 3
    Don't create the app via a global variable, this makes testing impossible. – Danny Varod Aug 02 '21 at 09:42
  • From my years of experience with Flask, taking inspiration from the `Factory Pattern` in the documentation worked best. The pattern is explained well https://flask.palletsprojects.com/en/2.0.x/tutorial/layout/. If you want me to expand on it, I can in an answer – shoaib30 Aug 02 '21 at 09:45
  • @Danny Yeah, my thoughts exactly. But I need the flask solution. – alexis Aug 02 '21 at 09:47
  • [flask cookiecutter](https://github.com/cookiecutter-flask/cookiecutter-flask) an simple example for structuring flask application – sahasrara62 Aug 02 '21 at 09:48
  • @shoaib30 thanks! Looking at that section again now. So is `create_app()` a magic name (like `app`) that triggers automatic behavior? I think I was missing that piece of the puzzle... – alexis Aug 02 '21 at 09:50
  • Sorry I posted the previous page of the tutorial, but to your question `create_app()` is expected by Flask to run the dev server. but when you're running it using a production WSGI server it isn't a magic word per say, you'll need to specify the function to call to create the app, hence any name would work – shoaib30 Aug 02 '21 at 09:55
  • Thanks! I guess I'll take you up on your suggestion to put together an answer for this, please :-) One more thing to check: Do I have to give up on `@app.route` decorators if I use a factory function? It would seem so, but Flask does a lot of magic... – alexis Aug 02 '21 at 10:01
  • 1
    Let me put it down in an answer, it might be useful for others too. give me a few mins – shoaib30 Aug 02 '21 at 10:05
  • https://stackoverflow.com/questions/67997606/how-can-i-run-flask-in-windows/68001417#68001417 – Ayse Aug 02 '21 at 10:16
  • @Ayse thanks for the link but it only covers the simplest flask scenario, and it only addresses development / debug usage. Not a duplicate, in any case. – alexis Aug 02 '21 at 10:34

2 Answers2

3

Disclaimer While this isn't the only struture for Flask, this has best suited my needs and is inspired from the Flask officials docs of using a Factory Pattern

Project Structure Following the structure from the Documentation

/home/user/Projects/flask-tutorial
├── flaskr/
│   ├── __init__.py
│   ├── db.py
│   ├── schema.sql
│   ├── auth.py
│   ├── blog.py
│   ├── templates/
│   │   ├── base.html
│   │   ├── auth/
│   │   │   ├── login.html
│   │   │   └── register.html
│   │   └── blog/
│   │       ├── create.html
│   │       ├── index.html
│   │       └── update.html
│   └── static/
│       └── style.css
├── tests/
│   ├── conftest.py
│   ├── data.sql
│   ├── test_factory.py
│   ├── test_db.py
│   ├── test_auth.py
│   └── test_blog.py
├── venv/
├── setup.py
└── MANIFEST.in

flaskr/, a Python package containing your application code and files.

flaskr will contain the factory to generate flask app instances that can be used by WGSI servers and will work with tests, orms (for migrations) etc.

flaskr/__init__.py contains the factory method

The Factory The factory is aimed at configuring and creating a Flask app. This means you need to pass all required configurations in one of the many ways accepted by Flask

The dev Flask server expects the function create_app() to be present in the package __init__.py file. But when using a production server like those listed in docs you can pass the name of the function to call.

A sample from the documentation:

# flaskr/__init__.py
import os

from flask import Flask


def create_app(test_config=None):
    # create and configure the app
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_mapping(
        SECRET_KEY='dev',
        DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'),
    )

    if test_config is None:
        # load the instance config, if it exists, when not testing
        app.config.from_pyfile('config.py', silent=True)
    else:
        # load the test config if passed in
        app.config.from_mapping(test_config)

    # ensure the instance folder exists
    try:
        os.makedirs(app.instance_path)
    except OSError:
        pass

    # a simple page that says hello
    @app.route('/hello')
    def hello():
        return 'Hello, World!'

    return app

when running a dev flask server, set environment variables as described

$ export FLASK_APP=flaskr
$ export FLASK_ENV=development
$ flask run

The Routes A Flask app may have multiple modules that require the App object for functioning, like @app.route as you mentioned in the comments. To handle this gracefully we can make use of Blueprints. This allows us to keep the routes in a differnt file and register them in create_app()

from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound

simple_page = Blueprint('simple_page', __name__,
                        template_folder='templates')

@simple_page.route('/', defaults={'page': 'index'})
@simple_page.route('/<page>')
def show(page):
    try:
        return render_template(f'pages/{page}.html')
    except TemplateNotFound:
        abort(404)

and we can modify the create_app() to register blueprint as follows:

def create_app(test_config=None):
    app = Flask(__name__, instance_relative_config=True)
    # configure the app
    .
    .
    .
    from yourapplication.simple_page import simple_page
    app.register_blueprint(simple_page)
    return app

You will need to locally import the blueprint to avoid circular imports. But this is not graceful when having many blueprints. Hence we can create an init_blueprints(app) function in the blueprints package like

# flaskr/blueprints/__init__.py
from flaskr.blueprints.simple_page import simple_page
def init_blueprints(app):
    with app.app_context():
        app.register_blueprint(simple_page)

and modify create_app() as

from flaskr.blueprints import init_blueprints
def create_app(test_config=None):
    app = Flask(__name__, instance_relative_config=True)
    # configure the app
    .
    .
    .
    init_blueprints(app)
    return app

this way your factory does not get cluttered with blueprints. And you can handle the registration of blueprints inside the blueprint package as per your choice. This also avoids circular imports.

Other Extensions Most common flask extensions support the factory pattern that allows you to create an object of an extension and then call obj.init_app(app) to initialize it with Flask App. Takeing Marshmallow here as an exmaple, but it applies to all. Modify create_app() as so -


ma = Marshmallow()

def create_app(test_config=None):
    app = Flask(__name__, instance_relative_config=True)
    # configure the app
    .
    .
    .
    init_blueprints(app)
    ma.init_app(app)
    return app

you can now import ma from flaskr in which ever file required.

Production Server As mentioned initialially, the production WSGI serevrs will call the create_app() to create instances of Flask. using gunicorn as an example, but all supported WSGI servers can be used.

$ gunicorn "flaskr:create_app()"

You can pass configurations as per gunicorn docs, and the same can be achieved within a script too.

shoaib30
  • 877
  • 11
  • 24
  • Thanks! I like the Blueprint trick to continue using the decorators. So Blueprints are unique and shared, `app` is not, if I got this right. Some clarification questions: 1) "using a production server like those listed in docs you can pass the name of the function to call." Can you clarify or give an example of what you mean, here? – alexis Aug 02 '21 at 10:54
  • 1
    @alexis when `app` is required, you can pass the function that will create the app as is `$ gunicorn "flaskr:create_app()"` , instead of `$ gunicorn flaskr:app` – shoaib30 Aug 02 '21 at 10:57
  • I see... this is a call and it evaluates to the `app` returned by the factory. Do you think you could expand on the access paths for configuration state (including things like host and port that I mentioned), or do you not have anything to add on that? – alexis Aug 02 '21 at 10:58
  • @alexis two parts to configuration - Think of Flask as an App and not a server. The configuration of Host and Port you ideally should do in the wsgi server used. The other configuration which is Flask specific, as recommended by [Flask](https://flask.palletsprojects.com/en/2.0.x/config/#configuration-best-practices) you can externalize into files of the format of your choice. And in `creatre_app()` you can import/use as shown in the function code – shoaib30 Aug 02 '21 at 11:03
  • 1
    @alexis for the purpose of development, to run Flask dev server, you can set port and host as a json file in the instance folder which is added to `.gitignore` and use `app.config.from_file()` or pass it as dictionary to `create_app()` – shoaib30 Aug 02 '21 at 11:10
  • Thanks! Separating out the server part makes things clearer conceptually. But iirc the wsgi launch executes the `'__main__'` block of the app module, so where do I put the code that sets or loads the config for development use? (Basically i'd like the code to contain one usable configuration, so I can launch it easily without additional scripts ... Is that just not the Flask way?) – alexis Aug 02 '21 at 11:42
  • @alexis you don't need a `__main__` block in Flask. I use [Waitress](https://docs.pylonsproject.org/projects/waitress/en/stable/arguments.html) you may use any WSGI server for both dev as well as prod and configure the same using environment variables or supported config files. You may still use the Flask dev server, just set the env var as shown in the answer along with `FLASK_RUN_PORT` or use `.flaskenv` https://flask.palletsprojects.com/en/2.0.x/cli/#environment-variables-from-dotenv – shoaib30 Aug 02 '21 at 12:03
0

What I did was:

class App:

    def __init__(self):
        # Various other initialization (e.g. logging, config, ...)
        ...
        self.webapp = self._start_webapp(self.app_name, self.app_port, self.log)
        pass

    def _start_webapp(self, app_name: str, app_port: Optional[int], log: logging):
        log.info('Running webapp...')
        webapp = Flask(app_name)
        # other flask related code
        ...
        webapp.run(debug=False, host='0.0.0.0', port=app_port)
        return webapp

    pass

if __name__ == '__main__':
    app = App()

This way you can add optional parameters to the init to override during tests or override via config change and even create additional types of endpoints in the same application, if you need.

Danny Varod
  • 17,324
  • 5
  • 69
  • 111