In this exercise, weâll do some refactoring. Previously, youâve put all your Flask code in a single app.py file. That was fine as long as you were learning. But itâs really not a good idea for most applications that have more than one or two routes. Additionally, youâll start working with database records soon, and as your codebase grows, itâs a good idea to structure it nicely.
In this exercise, you learn about blueprints as a way to structure your appâs code. They were introduced in Flask 2 and have made organizing your code a lot easier.
Separating App and Server
Before we get started with blueprints, letâs reorganize some of our code. Itâs a good idea to move all your application code inside its own folder and keep everything thatâs not directly related to the application logic outside of it.
Create a new folder called app. Then, move the following folders in there:
- static
- templates
Additionally, move the following files in there:
- app.py
- config.py
We purposely keep the venv folder separate. We also keep some general configuration files separate (such as .env, .gitignore, and requirements.txt).
Finally, create a new file in the root of our project and call it run.py. This file will be in charge of running our server. If we ever need to add configuration or parameters to the server, we can add them here.
This is what my folder structure now looks like:
We also need to make some adjustments to our code. Because we moved some files around, some imports will no longer point to the right place. Also, the run.py file should now have some of our server logic.
Letâs start with the imports.
Inside that /app/app.py file, change the config line from app.config.from_object('config')
to
app.config.from_object('app.config')
Thatâs because the config file is now in the designated app folder, and when the app is executed, itâll be executed from the run.py file in our root folder.
Remember, in Python, folders act kind of like modules in themselves. app
of app.config
refers to the /app folder. config
refers to the config.py file inside the /app folder.
Letâs address the run.py file we just created. At the bottom of the /app/app.py file, we have this code:
if __name__ == '__main__':
app.run()
Remove it entirely and paste it into the newly created run.py file. You see that in that file, we access the app
variable. So we need to import it. At the top of the file, add:
from app.app import app
Again, the app.app
refers to the path /app/app.py. The import app
refers to the variable app
inside that file thatâs defined with app = Flask(__name__)
.
Yes, itâs many app
s and can be a little confusing. But it helps to name these things in a consistent way to know where to find them later on.
Alright. With all those changes done, you should be able to run
python run.py
This should start up your Flask server as before, and everything should still work. Make sure thatâs the case before you move on to the next step!
Blueprints
Now, itâs time for the blueprints. Think of blueprints like sub-applications within our main Flask app. There are different ways to structure them. But a good approach is to build them around the main features of your application.
I will start by setting up a blueprint for our cookies pages for our app.
Create a new folder inside the app folder and call it cookies. In there, create a new file called routes.py. This is going to act as the controller (from the MVC pattern). So the final path will be:
- app/cookies/routes.py
Now, weâre going to do some copy-and-pasting.
Copy the cookies_data
dictionary (or list) and paste it in the /app/cookies/routes.py file. This is just some temporary data that we will replace in the upcoming exercises. But for now, letâs move it so that our code doesnât break. Once itâs copied, you can delete it from the app.py file.
Next, take the two routes cookie(slug)
and cookies()
including the decorators and all function content and copy them over to the /app/cookies/routes.py file (below the cookies_data
). Remove them from the app.py file.
There are a few things we have to fix in here.
First, we need to import a few things from flask
:
from flask import Blueprint, render_template
Notice how instead of Flask
we import Blueprint
.
As I mentioned before, Blueprint
works like a mini sub-application of our main Flask app. To use it, we initialize it similarly as we initialize our Flask
app. Below the import statements write:
blueprint = Blueprint('cookies', __name__)
As opposed to the Flask
class, with Blueprint
we added an extra parameter to specify the name of the blueprint. We need that if we want to load the specific blueprint later on. This should be unique, and you shouldnât have multiple blueprints with the same name.
Finally, we need to adjust the decorator methods to use @blueprint
instead of @app
.
The final /app/cookies/route.py could look something like this now:
from flask import Blueprint, render_template
cookies_data = {
'chocolate-chip' : {'name': 'Chocolate Chip', 'price': '$1.50'},
'oatmeal-raisin' : {'name': 'Oatmeal Raisin', 'price': '$1.00'},
'sugar' : {'name': 'Sugar', 'price': '$0.75'},
'peanut-butter' : {'name': 'Peanut Butter', 'price': '$0.50'},
'oatmeal' : {'name': 'Oatmeal', 'price': '$0.25'},
'salted-caramel' : {'name': 'Salted Caramel', 'price': '$1.00'},
}
blueprint = Blueprint('cookies', __name__)
@blueprint.route('/cookies/<slug>')
def cookie(slug):
if slug in cookies_data:
return '<h1>' + cookies_data[slug]['name'] + '</h1>' + '<p>' + cookies_data[slug]['name'] + '</p>'
else:
return 'Sorry we could not find that cookie.'
@blueprint.route('/cookies')
def cookies():
return render_template('cookies.html', cookies=cookies_data)
As the last step, we want to be able to import the blueprint in our main app.py file.
We could do that like this: from .cookies.routes import blueprint
. But that would cause issues later on if we add more blueprints. Because the blueprint
variable is already imported from the cookies
folder, we canât import it from other folders. We could solve that by just naming it more explicitly - for example: cookies_blueprint
. Thatâs a valid approach. But there is another more popular way.
Create a new file called __init__.py in the app/cookies folder. (Note there are two underscore before and after init
- thatâs important).
In that file, add just this one line:
from . import routes
This is standard Python code. If youâre not familiar with it, I recommend learning about Python modules and imports. But in short, Python treats folders like modules. Whenever you import a folder such as the cookies folder, if that folder contains an __init__.py file, that file is executed as the folder is imported.
In our case, we use that to be able to just import the cookies
folder and not routes
or blueprint
specifically.
Letâs do that now. Back in our /app/app.py file, at the top add this import statement:
from . import cookies
Now, somewhere below our app
definition (maybe right above the remaining routes) add this line:
app.register_blueprint(cookies.routes.blueprint)
As you may have guessed, the .register_blueprint()
function registers the blueprint, meaning it connects it with our main application. cookies
is what we just imported, and weâre accessing the routes.py file in that folder and get the blueprint
variable from it.
Blueprints and Templates
One last thing about creating blueprints. It may make sense to also organize your views (aka templates) by blueprints to keep things organized.
In your /app/templates folder, create a new folder called cookies. Now, move your cookies.html file into that folder and rename it to index.html. The last step is optional. But since this pageâs purpose is to display all our cookies, it acts as index of our cookies collection, and itâs considered best practice to name that view index.
You now need to also adjust the render_template
functions in your blueprintâs route.py file accordingly to:
return render_template('cookies/index.html', cookies=cookies_data)
You successfully created blueprints and refactored major parts of your application. Your app.py feels already much lighter.
As a next step, you should do the same thing with the index()
, about()
, and legal()
routes. Those could have their own blueprint called simple_pages for example. That blueprint could contain all the landing and legal pages for your website. Keep in mind that you may need to import
additional functions from flask
.
If you still have a url_for
function (for example because you used it with a redirect
), you need to make sure to adjust it from e.g. url_for('about')
to url_for('simple_pages.about')
. Our about
route is now part of the sub-module simple_pages
. Therefore, you need to prefix it with the module name.
In the end, the app.py should only import and initialize the different parts of your application. All of the business logic should live within the blueprints.
Here is what the final app.py looks like when it was made much smaller:
from flask import Flask
from . import cookies, simple_pages
app = Flask(__name__)
app.config.from_object('app.config')
# Blueprints
app.register_blueprint(cookies.routes.blueprint)
app.register_blueprint(simple_pages.routes.blueprint)
This is the /app/simple_pages/routes.py file:
from flask import Blueprint, render_template, redirect, url_for, send_file
blueprint = Blueprint('simple_pages', __name__)
@blueprint.route('/')
def index():
return render_template('simple_pages/index.html')
@blueprint.route('/about')
def about():
return 'I like cookies'
@blueprint.route('/about-me')
def about_me():
return redirect(url_for('simple_pages.about'))
@blueprint.route('/legal')
def legal():
return send_file('static/downloads/legal.txt', as_attachment=True)
(Note the changes of url_for
and the different import
s)
Factory Pattern
As the final step, we will apply something called the Factory Pattern. You can read more about it here.
The idea is to wrap the different parts of our app.py file in functions that can be called and tested (more on that later) individually.
Start with the blueprints. Letâs wrap those inside their own function.
def register_blueprints(app: Flask):
app.register_blueprint(cookies.routes.blueprint)
app.register_blueprint(simple_pages.routes.blueprint)
Side note on parameter typing: app
is defined as a parameter of the registr_blueprints
function. But you may notice the : Flask
. This is a type definition. It tells Python that the app
variable should be an instance of the Flask
class. Itâs a pattern that helps keep code clean and avoid mistakes of accidentally passing a parameter we wouldnât want to pass.
Since the registration code is now within a function, it needs to be called explicitly. Additionally, weâll also wrap the app
initialization inside a new create_app()
function. Within that function, we can also call the other function we just created. Here is the full app.py file:
from flask import Flask
from . import cookies, simple_pages
def create_app():
app = Flask(__name__)
app.config.from_object('app.config')
register_blueprints(app)
return app
# Blueprints
def register_blueprints(app: Flask):
app.register_blueprint(cookies.routes.blueprint)
app.register_blueprint(simple_pages.routes.blueprint)
Note the return app
statement. Thatâs important so that we can access the app
variable when we call create_app()
. So we can see the register_blueprints
functions being called. But where do we run the create_app()
?
Weâll do that in the run.py file. Change it to look like this:
from app.app import create_app
app = create_app()
if __name__ == '__main__':
app.run()
Conclusion
In this exercise, we didnât add any logic. In fact, your app should function the exact same way it did before you did all the refactoring in this exercise.
Your code is now much cleaner, much better organized, and ready to be extended with new features in the future. We did this step so late in the tutorial because it becomes much harder to comprehend how it all is connected whenever you abstract code into different files. So I hope by adding everything in a single file at first and only later refactoring it, you could get a solid understanding of how all the different parts of your app work.
Now you have a good foundation, we will add a few more features to our application.
đ Practice
But first, itâs time to practice refactoring with your second project!
- Move your app logic inside its own app folder within your project but keep the logic for the server within the root. Create a new run.py file to handle the server logic.
- Create blueprints for the different features of your app. Try to do it one by one and not everything at once and always test in between. This way, you catch whenever you make a mistake.
- Apply the factory pattern to your app.py and adjust the run.py file accordingly.