Update: Updated May 2018 to work with Python 3 on GitHub.
This post is about creating Python Flask web pages that can be asynchronously updated by your Python Flask application at any point without any user interaction. We’ll be using Python Flask, and the Flask-SocketIO plug-in to achieve this. The working application is hosted on GitHub.
What I want to achieve here is a web page that is automatically updated for each user as a result of events that happened in the background on my server system. For example, allowing events like a continually updating message stream, a notification system, or a specific Twitter monitor / display. In this post, I show how to develop a bare-bones Python Flask application that updates connected clients with random numbers. Flask is an extremely lightweight and simple framework for building web applications using Python.
If you haven’t used Flask before, it’s amazingly simple, and to get started serving a very simple webpage only requires a few lines of Python:
# Basic Flask Python Web App from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run()
Running this file with python application.py
will start a server on your local machine with one page saying “Hello World!” A quick look through the documentation and the first few sections of the brilliant mega-tutorial by Miguel Grinberg will have you creating multi-page python-based web applications in no time. However, most of the tutorials out there focus on the production of non-dynamic pages that load on first accessed and don’t describe further updates.
For the purpose of updating the page once our user has first visited, we will be using Socket.io and the accomanying Flask addon built by the same Miguel Grinberg, Flask-Socketio (Miguel appears to be some sort of Python Flask God). Socket IO is a genius engine that allows real-time bidirectional event-based communication. Gone are the days of static HTML pages that load when you visit; with Socket technology, the server can continuously update your view with new information.
For Socket.io communication, “events” are triggered by either the server or connected clients, and corresponding callback functions are set to execute when these events are detected. Implementing event triggers or binding event callbacks are very simply implemented in Flask (after some initial setup) using:
# Basic blask server to catch events from flask import Flask, render_template from flask.ext.socketio import SocketIO, emit app = Flask(__name__) app.config['SECRET_KEY'] = 'secret!' socketio = SocketIO(app) @socketio.on('my event') # Decorator to catch an event called "my event": def test_message(message): # test_message() is the event callback function. emit('my response', {'data': 'got it!'}) # Trigger a new event called "my response" # that can be caught by another callback later in the program. if __name__ == '__main__': socketio.run(app)
Four events are allowed in the @socketio.on() decorator – ‘connect’, ‘disconnect’, ‘message’, and ‘json’. Namespaces can also be assigned to keep things neatly separated, and the send() or emit() functions can be used to send ‘message’ or custom events respectively – see the details on Miguel’s page.
On the client side, a little bit of JavaScript wizardry with jQuery is used to handle incoming and trigger outgoing events. I would really recommend the JavaScript path on CodeSchool if you are not familiar with these technologies.
// Client Side Javascript to receive numbers. $(document).ready(function(){ // start up the SocketIO connection to the server - the namespace 'test' is also included here if necessary var socket = io.connect('http://' + document.domain + ':' + location.port + '/test'); // this is a callback that triggers when the "my response" event is emitted by the server. socket.on('my response', function(msg) { $('#log').append('<p>Received: ' + msg.data + '</p>'); }); //example of triggering an event on click of a form submit button $('form#emit').submit(function(event) { socket.emit('my event', {data: $('#emit_data').val()}); return false; }); });
And that, effectively, is the bones of sending messages between client and server. In this specific example, we want the server to be continually working in the background generating new information, while at the same time allowing new clients to connect, and pushing new information to connected clients. For this purpose, we’ll be using the Python threading module to create a thread that generates random numbers regularly, and emits the newest value to all connected clients. Hence, in application.py, we define a thread object that will continually create random numbers and emit them using socketIO separately to the main flask process:
#Random Number Generator Thread thread = Thread() thread_stop_event = Event() class RandomThread(Thread): def __init__(self): self.delay = 1 super(RandomThread, self).__init__() def randomNumberGenerator(self): """ Generate a random number every 1 second and emit to a socketio instance (broadcast) Ideally to be run in a separate thread? """ #infinite loop of magical random numbers print("Making random numbers") while not thread_stop_event.isSet(): number = round(random()*10, 3) print number socketio.emit('newnumber', {'number': number}, namespace='/test') sleep(self.delay) def run(self): self.randomNumberGenerator()
In our main Flask code then, we start this RandomThead running, and then catch the emitted numbers in Javascript on the client. Client presentation is done, for this example, using a simple bootstrap themed page contained in the Flask Template folder, and the number handling logic is maintained in the static JavaScript file application.js. A running list of 10 numbers is maintained and all connected clients will update simultaneously as new numbers are generated by the server.
//Client-side Javascript code for handling random numbers $(document).ready(function(){ //connect to the socket server. var socket = io.connect('http://' + document.domain + ':' + location.port + '/test'); var numbers_received = []; //receive details from server socket.on('newnumber', function(msg) { console.log("Received number" + msg.number); //maintain a list of ten numbers if (numbers_received.length >= 10){ numbers_received.shift() } numbers_received.push(msg.number); numbers_string = ''; for (var i = 0; i < numbers_received.length; i++){ numbers_string = numbers_string + '<p>' + numbers_received[i].toString() + '</p>'; } $('#log').html(numbers_string); }); });
And in the application.py file:
#Python code to start Asynchronous Number Generation @app.route('/') def index(): #only by sending this page first will the client be connected to the socketio instance return render_template('index.html') @socketio.on('connect', namespace='/test') def test_connect(): # need visibility of the global thread object global thread print('Client connected') #Start the random number generator thread only if the thread has not been started before. if not thread.isAlive(): print("Starting Thread") thread = RandomThread() thread.start()
And that’s the job. Flask served web pages that react to events on the server. I was tempted to add a little graph using HighCharts or AmCharts, but I’m afraid time got the better of me. Perhaps in part2. The final output should look like this:
You can find all of the source code on GitHub, with instructions on how to install the necessary libraries etc. Feel free to adapt to your own needs, and leave any comments if you come up with something neat or have any problems.
This functionality is documented in the original documentation for Flask-SocketIO. The documentation and tutorials are quite comprehensive and worth working through if you are interested in more.
Hi,
Why is it only within the console are the random numbers being generated, but nothing on the browser after
Number list:
Thanks much appreciated.
Seb
Or simply updating it within the same TextField, similar to how real time stock quotes are displayed?
Hi,
Thanks for this tutorial, I downloaded your code, run it but got this :
(flaskiotest) C:\Users\alotfi\PycharmProjects\async_flask-master>python application.py
WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.
* Running on http://127.0.0.1:5000/
* Restarting with reloader
WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.
Thanks, your help is appreciated.
I Have the same problem, not element in number list…
any response ?
Thnx
Hi guys, I think some version issues are affecting this. Can you try putting in the following code and see if it works at the top of the file:
import eventlet
eventlet.monkey_patch()
That last part is essential !
Hi guys,
download the jquery and socketio files and put this in static directory.
change this code in the head:
change this:
import eventlet
eventlet.monkey_patch()
from flask_socketio import SocketIO, emit
and this:
#turn the flask app into a socketio app
async_mode = “eventlet”
socketio = SocketIO(app, async_mode=async_mode)
and it works
Which jquery and sockio files? download from where?
I also can’t find a python module called eventlet. And, monkey.py appears to be in the gevent module. No sign of the monkey_patch() in gevent.
My bad, I found the eventlet module and applied this to the application.py:
import eventlet
eventlet.monkey_patch()
It works without adding this:
async_mode = “eventlet”
socketio = SocketIO(app, async_mode=async_mode)
It also works when it is added…
BTW: I updated the index.html file to:
also modified the print statements to Python 3 style.
So, I can report it now works in python 3.5.2.
I have the same issues as SebastianC. Is there somebody that can help us with this issue?
Running python 3.5.1 on windows 10. Made appropriate changes to application.py but no other files (should i?). index.html displays nicely but no numbers updated. Can you please help out?
How would one go about if one wanted to have not the same random numbers for each client, but a different set of data for each?
Error on line :- async_mode=”eventlet”
–SyntaxError: Non-ASCII character ‘\xe2’ in file application.py on line 26, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
after adding codecs at top:- still error occurred on same line saying :-
SyntaxError: ‘ascii’ codec can’t decode byte 0xe2 in position 0: ordinal not in range(128)
If possible please help
@chirag you need to replace the ” with ” that wraps eventlet. you cant copy and paste
Thank you for a great tutorial!
Could someone please answer the following question which i also see above:
Which jquery and sockio files? download from where?
I am trying this for the first time, not seeing any numbers probably because i haven’t done the above step.
Hi there. Thank you very much for this tutorial!
One issue I found is that in an attempt to clone the repository and then set up my virtual environment, you indicate to : ./flaskiotest/Scripts/activate
which returns : bash: ./flaskiotest/Scripts/activate: No such file or directory
At closer inspection, there does not appear to be a Scripts directory within /flaskiotest
I did, however, find ‘activate’ inside /flaskiotest/bin
I tried : ./flaskiotest/bin/activate
which returns : bash: ./flaskiotest/bin/activate: Permission denied
Any insight greatly appreciated.
That said, if I should instead try to run without virtual environment, are the only dependencies Flask, Flask_SocketIO, and the associated .js (and css) ?
Regarding my previous reply…
Instead of :
I tried:
source flaskiotest/bin/activate
and the virtual env appears to be working.
Now on to the fun stuff!
I noticed that a missing closing for
[…] https://www.shanelynn.ie/asynchronous-updates-to-a-webpage-with-flask-and-socket-io/ […]
[…] https://www.shanelynn.ie/asynchronous-updates-to-a-webpage-with-flask-and-socket-io/ […]
Something else to add on, In my case, I added this modification : when the client disconnects, also stop the thread using :
if thread.isAlive():
global thread_stop_event
thread_stop_event.set()
print(‘Disconected & thread stopped’)
inside test_disconnects() because I noticed the thread continued in the server even after client closes the browser.
And to restart it, something like:
thread_stop_event.clear() inside the test_connect() function, I noticed you initialised that variable and only performed checks but never manipulated it.
also ask for this question https://github.com/shanealynn/async_flask/issues/9
Saved the day! was struggling with this for hours!
How can you change the code to only show the most recent random number, not all 10?
replace the for loop in js code where the logic of getting all 10 numbers lies with something you need(like only the most recent number).
This can’t handle multiple clients
This is by far the simplest and clearest working up-to-date example of Flask-SocketIO – Thank you.
incredible website and great content creator. There are very successful contents, I liked them very much and I wish you continued success.