Implementing Socket IO event handling via Blueprint in Flask-SocketIO - without using a single global socketio object. · GitHub

Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@astrolox
Last active April 5, 2024 03:08
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save astrolox/445e84068d12ed9fa28f277241edf57b to your computer and use it in GitHub Desktop.
Save astrolox/445e84068d12ed9fa28f277241edf57b to your computer and use it in GitHub Desktop.
Implementing Socket IO event handling via Blueprint in Flask-SocketIO - without using a single global socketio object.
"""
Flask web application factory. Creates and returns the flask app object.
@see http://flask.pocoo.org/docs/1.0/
@author Brian Wojtczak
"""
import logging
import os
from flask import Flask
from flask_socketio import SocketIO
def create_app(test_config: dict = None) -> Flask:
# Application instance
app = Flask(__name__)
# SocketIO extension
socketio = SocketIO()
socketio.init_app(app)
# Functionality via blueprints
from . import auth, events
app.register_blueprint(auth.bp)
app.register_blueprint(events.bp)
return app
"""
Dummy Flask Authentication Blueprint.
@author Brian Wojtczak
"""
import functools
import logging
from flask import Blueprint, request, session, abort
logger = logging.getLogger(__name__)
bp = Blueprint('auth', __name__)
def is_logged_in():
return True
def login_required(view):
"""
View decorator that sends an error to anonymous users.
Use the error handler to provide a friendly user experience.
"""
@functools.wraps(view)
def wrapped_view(**kwargs):
if not is_logged_in():
abort(401)
return view(**kwargs)
return wrapped_view
@bp.before_app_request
def load_logged_in_user():
pass # Not implemented
"""
Flask Blueprint for handling events on the SocketIO stream.
@author Brian Wojtczak
"""
import functools
import logging
import time
from flask import request
from flask_socketio import emit, ConnectionRefusedError, disconnect
from .auth import is_logged_in
from .io_blueprint import IOBlueprint
logger = logging.getLogger(__name__)
bp = IOBlueprint('events', __name__)
def authenticated_only(f):
@functools.wraps(f)
def wrapped(*args, **kwargs):
if not is_logged_in():
disconnect()
else:
return f(*args, **kwargs)
return wrapped
@bp.on('connect')
def connect():
if not is_logged_in():
raise ConnectionRefusedError('unauthorized!')
emit('flash', 'Welcome ' + request.remote_user) # context aware emit
@bp.on('echo')
@authenticated_only
def on_alive(data):
logger.debug(data)
emit('echo', data) # context aware emit
@bp.on('broadcast')
@authenticated_only
def on_broadcast(data):
logger.debug(data)
bp.emit('broadcast', data) # bp.emit same as socketio.emit
"""
A Flask Blueprint class to be used with Flask-SocketIO.
This class inherits from the Flask Blueprint class so that
we can use the standard Blueprint interface.
Derived from https://github.com/m-housh/io-blueprint
Original work by Michael Housh, mhoush@houshhomeenergy.com
Modified by Brian Wojtczak
@author Brian Wojtczak
"""
# noinspection PyPackageRequirements
import socketio
from flask import Blueprint
class IOBlueprint(Blueprint):
def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs)
self.namespace = self.url_prefix or '/'
self._socketio_handlers = []
self.socketio = None
self.record_once(self.init_socketio)
def init_socketio(self, state):
self.socketio: socketio.Client = state.app.extensions['socketio']
for f in self._socketio_handlers:
f(self.socketio)
return self.socketio
def on(self, key):
""" A decorator to add a handler to a blueprint. """
def wrapper(f):
def wrap(sio):
@sio.on(key, namespace=self.namespace)
def wrapped(*args, **kwargs):
return f(*args, **kwargs)
return sio
self._socketio_handlers.append(wrap)
return wrapper
def emit(self, *args, **kwargs):
self.socketio.emit(*args, **kwargs)
Copyright 2020 Brian Wojtczak
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@Beefster09
Copy link

License?

@astrolox
Copy link
Author

astrolox commented Jan 12, 2021

License?

Added MIT license declaration for you.

@avijoni
Copy link

avijoni commented Dec 17, 2021

I know this is old post, but still. Is this still working? Do you maybe have any idea why this error happens:
if "." in name:
TypeError: argument of type 'IOBlueprint' is not iterable

I get same error with your code and with integrating IOBlueprint into my test project. Thanks in advance.

@astrolox
Copy link
Author

I used it in a few projects - it works there, but I've not worked on those projects for a while though, so still using older versions of libraries.

eventlet==0.25.1
Flask==1.0.3
Flask-SocketIO==4.1.0
python-socketio==4.2.0

@astrolox
Copy link
Author

Also please make sure you're using Python 3.

@avijoni
Copy link

avijoni commented Dec 19, 2021

Thank you Brian. It works with those versions. I guess something was changed in some of those modules later I guess. Will deep dive and try to find a fix.

@enjoythecode
Copy link

@avijoni

I believe I have found a fix for this. On line 23, delete the first argument to super(). So:

# go from this
class IOBlueprint(Blueprint):

    def __init__(self, *args, **kwargs):
        super().__init__(self, *args, **kwargs)
        # <rest of __init__ is the same>

# to this
class IOBlueprint(Blueprint):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)                   # <= notice the lack of the first `self` argument here
        # <rest of __init__ is the same>

@astrolox
Copy link
Author

Thanks @enjoythecode

@avijoni
Copy link

avijoni commented Mar 4, 2022

@enjoythecode Thanks. I tested it today and I can confirm fix works.

@wuzzy1212
Copy link

Is there anything else that needs to be taken into consideration if this is using https/nginx/gunicon, still can't seem to catch the emits on the server coming from the client, although server->client works as it should. Great code though, thank you.

@astrolox
Copy link
Author

Is there anything else that needs to be taken into consideration if this is using https/nginx/gunicon, still can't seem to catch the emits on the server coming from the client, although server->client works as it should. Great code though, thank you.

Just the normal considerations for web sockets.
https://www.nginx.com/blog/websocket-nginx/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment