Dynamically Update Your Flask Web Pages Using Turbo-Flask

Posted by
on under

How can you update a part of a web page dynamically, without the user having to hit the refresh button on the browser?

This is a question that is frequently asked on the Internet. If you have some knowledge of JavaScript this is relatively easy to do, more so if you use a front end web framework such as React or Vue. But what if you have a standard web application written in Flask and Jinja templates?

In this article I'm going to introduce you to my Turbo-Flask extension, which will allow your Flask application to easily push asynchronous page updates to the browser, without having to write any JavaScript code. In the screenshot below, note how the CPU load numbers update without the user doing anything.

Turbo-Flask Demo Application

What is Turbo-Flask?

Turbo-Flask is a Flask extension that integrates Hotwire's turbo.js JavaScript library with your Flask application.

This library bundles a number of different features, all with the goal of making server generated web pages behave more like single-page applications, but without requiring the application to write any front end code in JavaScript. Here is a list of the four modules included in turbo.js:

  • Turbo Drive: to accelerate page navigation by transparently updating pages in the background, without letting the browser ever do a full page reload.
  • Turbo Frames: to only update predefined parts of the page when the user clicks on a link or submits a form.
  • Turbo Streams: to let the server-side application update parts of the page by submitting HTML fragments to the client.
  • Turbo Native: to wrap your application as a native iOS or Android app. This section of turbo.js is out of scope for this article and I have very little experience with it, so I will not discuss it at all.

If you are interested in the Turbo Drive, and Frames features, I suggest you review the turbo.js documentation links above, as these do not require a Flask integration and can be used directly in your Jinja templates.

In this article I want to concentrate on the Turbo Streams feature, which is, in my opinion, the most interesting of the set.

An Example Application

This is going to be a hands-on tutorial, so let's create a short Flask application to which we can later add Turbo-Flask. If you are too lazy to type or copy/paste the code examples below, you can find the complete code for this article in the Turbo-Flask repository on GitHub.

To begin, create a directory in which you will work on this application. You may also want to create a virtual environment and activate it. Once you are ready, install Flask and Turbo-Flask:

(venv) $ pip install flask turbo-flask

Note: The Turbo-Flask package requires Flask version 2.

The Flask application

The code for the base application is shown below. Copy it to a file named app.py:

import random
import re
import sys
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/page2')
def page2():
    return render_template('page2.html')

@app.context_processor
def inject_load():
    if sys.platform.startswith('linux'): 
        with open('/proc/loadavg', 'rt') as f:
            load = f.read().split()[0:3]
    else:
        load = [int(random.random() * 100) / 100 for _ in range(3)]
    return {'load1': load[0], 'load5': load[1], 'load15': load[2]}

The application defines two routes on the / and /page2 URLs. Both pages are implemented with Jinja templates named index.html and page2.html.

So far this does not show anything out of the ordinary. The last part of the application is something you may not be familiar with. The inject_load() function is decorated with the @app.context_processor decorator from Flask, which allows the application to add symbols to the Jinja scope. The dictionary that this function returns contains three keys, load1, load5 and load15, which Flask will make available to all Jinja templates rendered with the render_template() function.

The three load numbers are a standard metric that can be obtained in Unix based systems. These numbers give you the CPU load of the system in the last 1, 5 and 15 minutes. The method that I use to obtain these numbers is specific to Linux servers:

        with open('/proc/loadavg', 'rt') as f:
            load = f.read().split()[0:3]

To ensure that you can use this example application under other operating systems, the function provides an alternative implementation that just generates three fake load numbers:

        load = [int(random.random() * 100) / 100 for _ in range(3)]

The Base Template

Since the application will have two pages with largely the same structure, it is a good idea to extract the base layout into a base template that both pages can inherit from. Here is the base.html template, which you will need to store in a templates sub-directory:

<!doctype html>
<html>
  <head>
    <title>Turbo-Flask Streams Demo</title>
    <style>
      .load {
        float: right;
        width: 300px;
        border: 1px solid black;
        margin-left: 10px;
        margin-bottom: 10px;
        padding: 10px;
      }
      .load th, .load td {
        padding: 6px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    {% include "loadavg.html" %}
    {% block content %}{% endblock %}
  </body>
</html>

The base template defines a few styles that create the design of the box with the load average metrics as you see in the screenshot at the start of the article. In particular, note how the float: right CSS attribute will make this part of the page appear on the top right corner of the page, wrapped with content.

The body of the template includes the loadavg.html template, which implements this box, and then defines a content Jinja block that the derived pages can use to provide their page bodies.

The Load Averages Template

The template referenced by the base template implementes the box that shows the three load average values as a table. Save the following page in the templates directory with the name loadavg.html:

<div id="load" class="load">
  <table>
    <tr><th></th><th>1 min</th><th>5 min</th><th>15 min</th></tr>
    <tr><th>Load averages:</th><td>{{ load1 }}</td><td>{{ load5 }}</td><td>{{ load15 }}</td></tr>
  </table>
</div>

Here notice the load1, load5 and load15 variables, which were the ones injected into the Jinja context by the application.

The Page Templates

To complete the base version of the application we need to create the two templates that are rendered by the index() and page2() routes. Since the content of the pages does not really matter much, I created a placeholder page with some dummy text in them, plus a link to the other page. Below you can see the index.html and page2.html templates, which you should copy to the templates sub-directory:

{% extends "base.html" %}

{% block content %}
<h1>Main Page</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>You can visit <a href="/page2">Page 2</a>.</p>
{% endblock %}
{% extends "base.html" %}

{% block content %}
<h1>Page 2</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Go back to the <a href="/">home page</a>.</p>
{% endblock %}

Both templates inherit from the base.html template created earlier, which means that they are going to have the box with the CPU metrics automatically added in the top-right corner of the page.

Running the Application

The application is now complete. You can start it with the flask run command:

(venv) $ flask run

Open a web browser and type http://localhost:5000 in the address bar. You should now have a two-page application that shows (static) CPU metrics in the top-right corner of the page.

Turbo-Flask Static Demo

Configuring Turbo-Flask

The goal is to have the CPU load values update every five seconds without the user having to refresh the page. The first step in achieving this goal is to add the Turbo-Flask extension to the application.

Turbo-Flask is initialized like most other Flask extensions. In the imports section of app.py, import the Turbo class:

from turbo_flask import Turbo

Right after the Flask application instance is initialized, create an instance of this class and initialized it by passing the Flask application instance:

turbo = Turbo(app)

In the base.html template, add {{ turbo() }} inside the <head> element:

<!doctype html>
<html>
  <head>
    ...
    {{ turbo() }}
  </head>
</html>

The call to the turbo() function in the base template initializes the turbo.js library. While Turbo Drive and Turbo Frames are not the focus of this article, you should know that this is all that is needed from the Python side to enable these modules in your application.

Adding a Background Updater Thread

And now we reach the most interesting part of this tutorial. We want the application to push updates to the CPU load numbers every 5 seconds, so we will need a background thread that can issue these updates to the connected clients.

A convenient way to start this background thread is to do it as a before_first_request handler, as this ensures that the thread will be up and running as soon as the first client connects. The changes to start a background thread on first request are shown below. Add these changes to app.py:

import threading

# ...

@app.before_first_request
def before_first_request():
    threading.Thread(target=update_load).start()

The update_load() function is where the page updates are going to be generated. The function is going to have a loop, and in each loop iteration an update will be pushed to all connected clients. Below you can see the implementation of this function, which you should also add to app.py:

import time

# ...

def update_load():
    with app.app_context():
        while True:
            time.sleep(5)
            turbo.push(turbo.replace(render_template('loadavg.html'), 'load'))

This function is going to call the render_template() function from Flask to generate the updated CPU metrics fragment, so it needs to have an application context. The loop includes a 5 second sleep that sets the frequency of the updates.

An update is sent to all clients with the turbo.push() method. This method has only one required argument, the stream to push to clients. A stream is composed of one or more page update operations. There are 5 supported operations that can be used with Turbo Streams:

  • Append, or turbo.append(content, target): add content at the end of target
  • Prepend, or turbo.prepend(content, target): add content at the start of target
  • Replace, or turbo.replace(content, target): replace target with content
  • Update, or turbo.update(content, target): replace the contents of target with content
  • Remove, or turbo.remove(target): remove target

When you want to push a single operation, you can pass the return value of the corresponding operation directly to turbo.push(), as in the example above. If you would like to send several updates in different parts of the page, you can also pass a list of several operations, and turbo.js will apply all the updates for you.

In this application we want to replace the old version of the CPU metrics with an updated version, so the most appropriate operation is turbo.replace(). The append and prepend options are very useful when the page update needs to add information, without removing any of the existing contents. The update operation is similar to replace, but only the contents of the target element are replaced, leaving the element itself alone. Finally, the remove operation is useful to delete a part of the page.

In all these operations the content argument is the HTML fragment that needs to be updated in the page, given as a string. When working with Flask this is easy to generate, you just need to have a template that renders only this section of the page. In this application we already have the CPU metrics in a separate template because we needed to include it in two different pages, so all we need to do to generate the HTML fragment is to render this template directly in a render_template() call.

The target argument accepted for these operations is the id of the target element in the page that receives the update. If you review the loadavg.html template you will notice that the top-level <div> in this template has id="load".

After you make the updates to the application, restart the Flask server and refresh the page on your browser. You should now see the CPU load numbers update at 5 second intervals. It was easy, right?

The turbo.push() method has an optional argument called to, that can be used to specify which client(s) should receive the update. in this application this argument was not included because the update needs to go to all connected clients. If you are interested in sending updates to a subset of the connected clients the to argument, along with the @turbo.user_id decorator will come in handy.

Great, But How Does This Work?

You may be curious to know how does the turbo.push() method do its magic.

One of the things that the {{ turbo() }} call added in the base template does is open a WebSocket connection with the server. This connection is transparent to the application, as the Turbo-Flask extension manages it for you.

A WebSocket connection is bi-directional, so the server can send data to the client without the client having to ask for it. The turbo.push() method takes advantage of this WebSocket connection to submit your page updates. The turbo.js library is listening on this connection and whenever data is received from the server it executes the operations included in the stream.

Deployment Considerations

Turbo-Flask tries to make the set up and use of the WebSocket endpoint completely transparent to your application. At least during development, you should not need to worry about this, as the extension takes care of integrating WebSocket support with the Flask development web server.

If you intend to deploy a Turbo-Flask application to production you will need to decide on a deployment strategy that is compatible with WebSocket. There are 5 different production-ready configurations that are supported:

  • Gunicorn with the threaded worker
  • Gunicorn with the eventlet worker
  • Gunicorn with the gevent worker
  • The eventlet WSGI web server with monkey patching
  • The gevent WSGI web server with monkey patching

To learn more about these options, consult the deployment documentation for the Flask-Sock package, used by Turbo-Flask to implement the WebSocket route.

Conclusion

I hope this tutorial gave you a good overview of Turbo-Flask and turbo.js, and how to use them to trigger dynamic updates to your Flask web pages.

As mentioned a few times throughout the article, turbo.js is composed of a collection of utilities that speed up navigation, form submissions and updates to web pages rendered in the server, so I encourage you to read the turbo.js documentation to learn about how to take advantage of the remaining parts of this library.

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

84 comments
  • #51 Denis said

    Hello Miguel, thanx for all the job You're doing for Flask. It seems thousands of people loved Flask because of your clear and simple explanations.
    This chapter for Turbo can solves the probleme of the page dinamiquel update. But I use blueprints and can't find a solution: @blueprint.app_context_processor start a thread, but this part:
    def update_load(app):
    with app.app_context(): #app_context
    while True:
    sleep(3)
    # with app.test_request_context('/avito_scan.html'):
    turbo.push(turbo.replace(render_template('avitoscan-table.html'), 'offerstable'))
    generate an error RuntimeError: Working outside of request context.
    Clear that at this time it dosn't have a request context. I'e tried to use app.test_request_context('/avito_scan.html') - it doesn't work. Where to search a solution?
    I don't use url_for in updated form
    Thanx a lot

  • #52 Miguel Grinberg said

    @Denis: You have to look at your stack trace to determine what part of your application requires a request context. Creating a test request context is a really bad idea anyway, even if it worked.

  • #53 Shoghi said

    Hi Miguel your job with Flask is the best!!!
    This turbo-flask is a great tool.
    I'm trying to use it offline hosting the turbo.js file with my flask app, I downloaded the file and used the url parameter like this:
    {{ turbo(url="/static/assets/js/plugins/turbo.js") }}
    But I get this error:
    Uncaught TypeError: Turbo.connectStreamSource is not a function
    I had downloaded the turbo.js from github

  • #54 Miguel Grinberg said

    @Shoghi: My guess is that you downloaded the wrong file. See https://github.com/miguelgrinberg/turbo-flask/issues/26.

  • #55 Shoghi said

    That was the problem!!, I tried the ruby in rail version but that one did not worked either... my solution was download the CDN version using the inspect option with google chrome.... again this is a great great work, I hate using JS directly lol

  • #56 Chandan said

    Thank You @Miguel for this lovely post which helped me to explore the Flask's functionality of updating the webpage dynamically using the Gunicorn.

  • #57 Jochen Egger said

    @Miguel: thanks a lot for the outstanding work.
    I'm trying to get started, but it seems for a blueprint, things work slightly differently.
    I'm doing
    bp = Blueprint('nodemanager', name)
    turbo = Turbo(bp)

    When rendering the page, I receive:
    ws_route = app.config.setdefault('TURBO_WEBSOCKET_ROUTE',
    AttributeError: 'Blueprint' object has no attribute 'config'

    Could you please advise, how this can be fixed?

  • #58 Miguel Grinberg said

    @Jochen: Flask extensions are initialized with the Flask application instance, you cannot use a blueprint to initialize this (or any other) extension.

  • #59 Geniack said

    I am a bit late to the party, but maybe someone has an answer for me... I want to continously add new lines to a web page using turbo_flask. I tried to use the turbo.push(turbo.append()) it will add a new element one time but then just keeps replacing that one. Any idea how I can make it add new lines at the end?

  • #60 Miguel Grinberg said

    @Geniack: without seeing your code I can't really provide feedback.

  • #61 Hernan said

    Sadly it seems it doesn't work any more as 2.2.X
    You get the following error:
    @app.before_first_request is getting deprecated in 2.3, so is not allowed to been used.

  • #62 Miguel Grinberg said

    @Hernan: this is not an error, it is a deprecation warning. Everything continues to work. Also, this is an example application, not the turbo-flask extension itself.

  • #63 Chandan said

    Hi @Miguel,
    I have a question related to this. Does the Websockets in the background keep sending the load average values continuously(irrespective of whether load values changes or not) using Websocket emit OR it listens only for updates in the load average values and send the data to webpage ONLY IF THE LOAD AVERAGE VALUE CHANGES? I am trying to use Hotwire Turbo streams with Sanic Framework.

  • #64 Miguel Grinberg said

    @Chandan: you have control of this. If you want to push at regular intervals, you can. If you prefer to push when something changes, you can as well.

  • #65 Chandan said

    Hi @Miguel,
    Thanks for your reply. I think a better way would be to send updates only if changes occur instead of continuously sending data as that would be a better resource utilization.

  • #66 Miguel Grinberg said

    @Chandan: of course. The point I was trying to make is that your application decides when to push, the Turbo-Flask extension gives you a function you can use to push updates to the client, but when and how often you call it is entirely up to your application.

  • #67 Aaron SC said

    Is there any way to apply inline styles from javascript on the injected load? Or even just CSS files? I've noticed that the load gets injected into the DOM, so that makes it so that any applied CSS from a file or JS file (inline) is removed when the load is updated or replaced. How would I make it so that I can apply CSS to an updated or replaced load?

  • #68 Miguel Grinberg said

    @Aaron: This is a question for the turbo.js people, my package simply makes the library more integrated with Flask. But in any case, if the HTML that you return from Flask has styles, those will automatically be used when the DOM is updated. If you have a complex solution that uses JavaScript, then turbo.js is probably not the right library for you, since its main purpose is to reduce JavaScript use.

  • #69 Vivek Malhotra said

    Wow. Looks cool. I am new to python and just came across this. I have a rest API which returns a JSON response which is changing every second. I would like to create a simple wep app in python flask where I get the data from Rest API, filter what I want to display from the JSON response and display it in a table on a web page.

    However, this table should update automatically in real time without user refreshing the page. So for e.g. every 10 second the application should fire a call to rest API, get response and display it on the page.

    Can this be done using this solution?

  • #70 Vivek Malhotra said

    On the github repo there are no instructions on how to build and run the application. When I try to run it I get an error as below: Any idea what I need to do?

    vivek.malhotra@4FWGBW3-DT:~/dev/git-repos/turbo-flask$ flask run
    * Environment: production
    WARNING: This is a development server. Do not use it in a production deployment.
    Use a production WSGI server instead.
    * Debug mode: off
    Usage: flask run [OPTIONS]

    Error: Could not locate a Flask application. You did not provide the "FLASK_APP" environment variable, and a "wsgi.py" or "app.py" module was not found in the current directory.
    vivek.malhotra@4FWGBW3-DT:~/dev/git-repos/turbo-flask$

  • #71 Miguel Grinberg said

    @Vivek: I think you should invest a little bit of time and learn how to work with Flask before you start using Turbo-Flask. Following the first few chapters of my Flask Mega-Tutorial should give you the background knowledge you need to make more sense of Turbo-Flask.

  • #72 Chandan said

    Hi @Miguel,

    I am seeing some performance hits on webpage using the above turbo.push. Everything runs fine for few seconds and then the page becomes unresponsive and finally crashes(probably because of infinite white True loop ??). I see below messages in the browser console. Not sure why this happens? After page refresh it again shows up everything and then again after few seconds, page crashes.

    WebSocket connection to 'ws://<IP>/turbo-stream' failed: Insufficient resources
    
  • #73 Miguel Grinberg said

    @Chandan: I don't really know, but a good thing for you to do would be to try one of the examples in the Turbo-Flask repository. Do these cause the same problem for you?

  • #74 Chandan said

    Hi @Miguel,

    So I tested my stuff with the Turbo-Flask Repository and its working well. Looks like issue in my code where there are so many things I am using related to socketIO, session, DB etc. Thanks for your help for showing the correct path to troubleshoot this.

  • #75 Gerwin Smit said

    Thanks a lot!
    I used this as a reference for my school project and it helped me a ton with setting up Flask!

Leave a Comment