Structure Flask project with convention over configuration
Roman Imankulov
Posted on May 5, 2021
When people ask me what a should beginner Python developer choose, Flask or Django, to start their new web project, all other things being equal, I always recommend Django.
Unlike Flask, Django is opinionated. In other words, every time you need to make a choice in Flask, Django has the answer for you. Among others, Django outlines the practical project structure out of the box.
With Django, you split your project into applications. The applications themselves have a well-defined structure: views.py, models.py, all those things. What maintains and enforces the design is the convention over configuration approach: you put your code to specific well-known locations, and the framework discovers them.
Flask doesn’t impose anything like that. You can start with a single file. When the application grows, it’s up to you to provide the application structure. As a result, the app can quickly turn into an unmaintainable mess.
Official Flask documentation, following the non-opinionated principle, leaves it for developers to decide. Chapters Larger ApplicationsModular Applications with Blueprints don’t cover the topic entirely. To close the gap, people created their own guidelines. Google “flask project structure” to find a plethora of variants and suggestions. For example, there is a chapter a better application structure in the monumental Flask Mega-tutorial.
What bugs me about any Flask solution is the lack of support for conventions. If you’re like me, you have a function app() that manually imports and configures all the things from all the packages and blueprints. Looks familiar? This is dirty.
For every extension, you call init_app. For every application with a well-defined structure, you make a dance, adding a couple of lines with imports and registrations.
To somehow address the issue, I created a Python package roman-discovery. The package lets you declaratively define the conventions of your application and run a discover() function to apply their rules. It's not specific to Flask, but I created it primarily with Flask in mind.
For example, assuming that you store all the blueprints in the myproject/<app>/controllers.py, that’s how you can automatically register all of them.
Micro-framework-based projects are clean while they're small. Every micro-framework codebase I've seen, has a mess in the project initialization. With time, create_app() becomes filled with ad-hoc settings, imports-within-functions, and plug-in initializations.
The nature of create_app() leaves no place for the open-closed principle. We update this module every time we add a new plug-in, a new blueprint, or a new package.
# myproject/__ini__.py## A common Flask application. The code is based on the Flask Mega-Tutorial.defcreate_app(config_class=Config):
app=Flask(__name__)
app.config.from_object(config_class)
db.init_app(app)
migrate.init_app(app, db)
login.init_app(app)
mail.init_app(app)
bootstrap