Flask socket IO emit from another module - python

I have almost read every piece of article available on the internet but nothing seems to work for my case. I have installed flask-socketio and everything works fine until I emit the messages from a module other than app.py.
I have tried several ways to accomplish this and I have also read in the doc about it by using Redis but it also did not work for me. Here are the code snippets that I have.
app.py
from flask import Flask
from flask import request
from flask_socketio import send, SocketIO, emit, join_room
app = Flask(__name__)
# This is to stop force sorting in response, by default jsonify sorts the response keys alphabetically
app.config["JSON_SORT_KEYS"] = False
socketio = SocketIO(app, cors_allowed_origins="*")
#socketio.on('join')
def client_join_room(data):
print(type(data))
room = data['room']
join_room(room)
send('you have entered the room.', room=room)
#app.route('/msg')
def send_message():
socketio.emit("message", "Server message", room='my_room')
return "I got you."
if __name__ == '__main__':
socketio.run(host="0.0.0.0", port=5001, debug=True, app=app)
my_module.py
def some_method():
import app
app.socketio.emit("message", "Some information about process", room='my_room', broadcast=True)
Note that I have imported app inside the method because app.py also imports my_module.py
I am able to join room.
When I call localhost:5001/msg it does emit to 'my_room'.
The emit does not work inside my_module.py and I have no idea why.
I am consoling the messages that I get from the server at the front-end so I know for sure which messages are received and which are not.
Also, the some_method() here is called by an API request from app.py. Just in case if that is relevant.
I have made logger=True and then I get this message printed on the terminal for each emit call. Even with the one inside some_method()
emitting event "message" to my_room [/]
Does that mean message is actually sent? If yes, then why am I not getting it in the jquery at front-end.
This is what I am doing in html page
$(document).ready(function () {
// start up the SocketIO connection to the server
var socket = io.connect('http://localhost:5001/');
// this is a callback that triggers when the "message" event is emitted by the server.
socket.on('message', function(msg){
console.log(msg)
});
socket.emit('join', {room: 'my_room'});
});

Please try and install Redis and eventlet for asynchronous calls and to send messages from other modules. As described in the documentation then you can change your line in app.py to
socketio = SocketIO(app, cors_allowed_origins="*", message_queue='redis://', async_mode='eventlet')

Related

Using emit in route Flask python

I am having trouble using emit in my route, I have tried many things but nothing seems to be working. My code is as follows:
app.py:
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
#app.route('/abc')
def index():
emit('message', {'data': 'Demo'})
return render_template('index.html')
#socketio.on('disconnect')
def on_disconnect():
print('Client disconnected')
if __name__ == '__main__':
socketio.run(app)
index.html:
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous"></script>
<script>
var socket = io();
socket.on('connect', function () {
console.log("Connected")
});
socket.on('message', function (msg) {
console.log(msg.data);
});
</script>
(?) How can I use emit in route?
Your design is flawed, since you are emitting too early.
Here is the sequence of events:
Client connects to the /abc route from the browser
The index() route runs and calls emit(). At this point the client is not connected, so it does not receive the message.
The index() function returns the index.html template to the browser.
The browser renders the HTML page, and as part of that executes the JavaScript code in it, which makes a Socket.IO connection to the server.
What you can do to verify that the emit is working is open this route in multiple browser tabs. When you open the first one nothing will happen, but when you open the second tab, the client on the first tab is going to receive the emit, since it is already connected. When you open the third tab, tabs 1 and 2 will receive the emit.

Flask, FlaskSocketIO - RuntimeError: Cannot obtain socket from WSGI environment

When I try to use the functionality that uses websockets in my application, I get this error in the console:
File "/Users/user/venv/lib/python3.7/site-packages/simple_websocket/ws.py", line 138, in __init__
raise RuntimeError('Cannot obtain socket from WSGI environment.')
RuntimeError: Cannot obtain socket from WSGI environment.
I also get this error in the browser console:
WebSocket connection to 'ws://localhost:5000/socket.io/?EIO=4&transport=websocket&sid=40NYzDgGYStMR0CEAAAJ' failed:
I tried using gevent, gevent-websocket, and eventlet, but this created other issues, and I'm not sure that I need to use gevent or eventlet for this use case.
Here's the rest of the relevant code:
__ init __.py
from flask_socketio import SocketIO
...
socketio = SocketIO()
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(Config)
socketio.init_app(app, cors_allowed_origins='*')
...
return app
app.py
from app import create_app, socketio
app = create_app()
if __name__ == '__main__':
socketio.run(app)
routes.py
This only accepts POST requests because I send data to this route from a Celery task
from app import socketio
...
#main_bp.route('/send_message', methods=['POST'])
def send_message():
...
socketio.emit('test_message', {'msg':'Test'}, namespace='/test')
return 'Results Sent'
index.html
var socket = io.connect('http://localhost:5000/test');
socket.on('connect', function(){
console.log('Connected to socket')
});
socket.on('test_message', function(message){
console.log('Received test message');
}
)
Note that in the browser console I'll see "Connected to socket", but not "Received test message"
You are using the simple-websocket package. This package has a list of supported web servers. The error indicates that you are using a web server that is not in the supported list.
Supported web servers are:
The Flask dev server (for development purposes only, of course)
Gunicorn
Eventlet
Gevent
From this list, it seems your only choice for a production web server is Gunicorn, since you say that eventlet/gevent won't work for your needs.
The use of Gunicorn is covered in the documentation. In that section, simple-websocket is the third option mentioned. Here is the example start up command:
gunicorn -w 1 --threads 100 module:app
Maybe you can turn on the logger and the engine.io logger. that qould give you an idea what is the issue.
don't use eventlet and gevent together. use anyone and uninstall the other.

Notification from Flask to JS using socket.IO

I'm using the Flask-SocketIO library which works fine but I need to send a notification with emit to the outside of a socket.io decorator and it's a real pain. Looking at the solutions, many people use rabbitmq or redis but I don't know how to use them.
Here's my code :
from flask import Flask, render_template
from flaskwebgui import FlaskUI
from flask_socketio import SocketIO, emit
app = Flask(__name__)
async_mode = None
app.config['SECRET_KEY'] = 'hello'
socketio = SocketIO(app, async_mode=async_mode, message_queue='amqp:///socketio')
def run_sock():
socketio.run(app, debug=True)
ui = FlaskUI(app, fullscreen=True, server=run_sock,)
#app.route("/")
def index():
return render_template('index.html')
#socketio.on('test', namespace='/test')
def test():
print("test")
if __name__ == "__main__":
ui.run()
io = SocketIO(message_queue='amqp:///socketio')
io.emit('test_emit', {'data': 'toto'}, namespace='/test')
My JS front-end never gets the test_emit message, how do I do?
The problem with your emit is that it appears below the ui.run() call, which does not return until you close the application. Move the emit to any function in your application that executes while the server is running (such as a Flask view function) and it should work just fine.
Why do you have two SocketIO objects in the same process? The socketio instance that you defined near the top of the script can be used anywhere within the process, no need to create a second instance. You do not need to use a message queue for this problem, since you have all the usages of Socket.IO within a single process.

Flask Blueprint application - How to get application context within mqtt on_message callback

I have created a flask application using Blueprints.
This application receives data via paho.mqtt.client.
This is also the trigger to processes the data and run processes afterwards.
'system' is a blueprint containing mqtt.py and functions.py
functions.py contains the function to process the data once received
mqtt.py contains the definition of the mqtt client
mqtt.py
from app.system import functions
import paho.mqtt.client as mqtt
#....
def on_message(mqttc,obj,msg):
try:
data = json.loads(msg.payload.decode('utf-8'))
# start main process
functions.process(data)
except Exception as e:
print("error: ", e)
pass
Once I receive data and the on_message callback is triggered I get an out of application context error:
error: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
How can i get the application context within the on_message callback?
I tried importing current_app and using something like this
from flask import current_app
#...
def on_message(mqttc,obj,msg):
try:
data = json.loads(msg.payload.decode('utf-8'))
app = current_app._get_current_object()
with app.app_context():
# start main process
functions.process(data)
I still get the same error
There is this package - https://flask-mqtt.readthedocs.io/en/latest/ - that might help, but it only works with one worker instance.
Most of the time you set the application context when you create the app object.
So wherever you create your app is where you should initialize the extension. In your case it sounds like functions.py needs mqtt.py to carry out its logic, so you should initialize your mqtt client in your application creation.
From the flask docs - http://flask.pocoo.org/docs/1.0/appcontext/
If you see that error while configuring your application, such as when
initializing an extension, you can push a context manually since you
have direct access to the app. Use app_context() in a with block, and
everything that runs in the block will have access to current_app.
def create_app():
app = Flask(__name__)
with app.app_context():
#init_db()
initialize mqtt client
return app

(flask + socket.IO) Result of emit callback is the response of my REST endpoint

Just to give a context here, I'm a node.JS developer, but I'm on a project that I need to work with Python using Flask framework.
The problem is, when a client request to an endpoint of my rest flask app, I need to emit an event using socket.IO, and get some data from the socket server, then this data is the response of the endpoint. But I didn't figured out how to send this, because flask needs a "return" statement saying what is the response, and my callback is in another context.
Sample of what I'm trying to do: (There's some comments explaining)
import socketio
import eventlet
from flask import Flask, request
sio = socketio.Server()
app = Flask(__name__)
#app.route('/test/<param>')
def get(param):
def ack(data):
print (data) #Should be the response
sio.emit('event', param, callback=ack) # Socket server call my ack function
#Without a return statement, the endpoint return 500
if __name__ == '__main__':
app = socketio.Middleware(sio, app)
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
Maybe, the right question here is: Is this possible?
I'm going to give you one way to implement what you want specifically, but I believe you have an important design flaw in this, as I explain in a comment above. In the way you have this coded, your socketio.Server() object will broadcast to all your clients, so will not be able to get a callback. If you want to emit to one client (hopefully not the same one that sent the HTTP request), then you need to add a room=client_sid argument to the emit. Or, if you are contacting a Socket.IO server, then you need to use a Socket.IO client here, not a server.
In any case, to block your HTTP route until the callback function is invoked, you can use an Event object. Something like this:
from threading import Event
from flask import jsonify
#app.route('/test/<param>')
def get(param):
ev = threading.Event()
result = None
def ack(data):
nonlocal result
nonlocal ev
result = {'data': data}
ev.set() # unblock HTTP route
sio.emit('event', param, room=some_client_sid, callback=ack)
ev.wait() # blocks until ev.set() is called
return jsonify(result)
I had a similar problem using FastAPI + socketIO (async version) and I was stuck at the exact same point. No eventlet so could not try out the monkey patching option.
After a lot of head bangings it turns out that, for some reason, adding asyncio.sleep(.1) just before ev.wait() made everything work smoothly. Without that, emitted event actually never reach the other side (socketio client, in my scenario)

Categories