How to unit test flask w/ flask-socket extension - python

I'm having trouble figuring out how to write a unit test for a Flask route that's decorated with #Sockets.route. With normal Flask routes, I can set up a test client as a context manager and use that to send requests, but I'm not sure what the analogue is for web sockets.
This is the code I'm trying to write a unit test for:
from flask_sockets import Sockets
sockets = Sockets(app)
#sockets.route('/test')
def echo_socket(ws):
while not ws.closed:
message = ws.receive()
ws.send('success')
The documentation for Flask-Sockets says I should use the gunicorn worker, but I'm not really clear how to get access to one of those from a unit test (just using the unittest module). I have something like:
def test_echo_socket:
# this is all incorrect
with my_app.app.test_client() as context:
context.get('/test')
self.assertEqual(context.response_code, '101')
Does anyone have any ideas how to get a websocket context for unit testing? Thanks.

Related

Flask keep live server working for tests (Selenium)

First of all I am aware of flask-testing library with LiveServerTestCase class but it hasn't updated since 2017 and GitHub full of issues of it not working neither on Windows or MacOs and I haven't found any other solutions.
I am trying to write some tests for flask app using selenium to validate FlaskForms inside this app.
Simple test like this:
def test_start(app):
driver.get("http://127.0.0.1:5000/endpoint")
authenticate(driver)
falls on selenium.common.exceptions.WebDriverException: Message: unknown error: net::ERR_CONNECTION_REFUSED error. (As far as I understood in my case app creates in #pytest.fixtures and immediately shuts down and I need to find a way to keep it running for the whole test duration)
My question is: Is it possible to to create some live server in each test that will remain working so I could call API endpoints via selenium?
Simple fixtures if it helps:
#pytest.fixture
def app():
app = create_app()
...
with app.context():
# creating db
...
yield app
also:
#pytest.fixture
def client(app):
"""Test client"""
return app.test_client()
Finally got it all working. My conftest.py
import multiprocessing
import pytest
from app import create_app
#pytest.fixture(scope="session")
def app():
app = create_app()
multiprocessing.set_start_method("fork")
return app
#pytest.fixture
def client(app):
return app.test_client()
Important note that using python <3.8 line multiprocessing.set_start_method("fork") is not necessary (as far as I understood in v.3.8 they refactored multiprocessing module so further upon without this line you would get pickle Error on windows and Mac).
And one simple test looks like
def test_add_endpoint_to_live_server(live_server):
#live_server.app.route('/tests-endpoint')
def test_endpoint():
return 'got it', 200
live_server.start()
res = urlopen(url_for('.te', _external=True))# ".te is a method path I am calling"
assert url_for('.te', _external=True) == "some url"
assert res.code == 200
assert b'got it' in res.read()
Also I am using url_for. The point is every time live server starts on a random port and url_for function generates url with correct port internally. So now live server is running and it is possible to implement selenium tests.

How to mock a rest API in python

I have a application running which some where in the midst uses some rest API call. Now for stress test I want to replace this API call with some mock server. Is there any way to do it.
Let me try to put it programmatically so it gets some clarity. I've a some server running at port say 8080
# main server
from flask import Flask
from myapp import Myapp
app = Flask(__name__)
#app.route("/find_solution", methods=["GET"])
def solution() :
return app.sol.find_solution(), 200
def start():
app.sol = Myapp()
return app
Now this Myapp
#myapp
import requests
class Myapp:
def __init__():
self.session = requests.Session()
def find_solution():
myparameters = {"Some parameter that I filled"}
return self.session.request('GET', 'http://api.weatherstack.com/current', params=myparameters)
Now here I want to replace behavior of http://api.weatherstack.com/current without modifying code. i.e some way where I can replace call to http:api.weatherstack.com/current to my local system server.
Any help of lead is appreciated. I am using ubuntu 20.04
So for your scenario if you want to test your api flask comes with mock test client feature.
test_client = app.test_client()
test_client.post('/find_solution', headers={"Content-Type": "application/json"}, data=data)
So for this scenario you can create test cases and get test client instance inside your test case and perform tests at api level. This is a light weight test method rather than the one proposed by you
Refer to the following link for official flask documentation
https://flask.palletsprojects.com/en/1.1.x/testing/#keeping-the-context-around
Cheers

Is there a better way to use rollbar error reporting with flask?

I just came across rollbar and wanted to include it in my Python project.
This is the standard way in which I am told to implement rollbar from the website.
import rollbar
rollbar.init('KEY')
try:
a = s
except:
rollbar.report_exc_info()
Is there a better way to implement this without going through all my try except blocks and replacing them with rollbar.report_exc_info()
Can there be a decorator implementation for this?
My current project is a Flask app that provides API's to end users.
Here's an example for rollbar integration in Flask apps.
https://github.com/rollbar/rollbar-flask-example/blob/master/hello.py
#app.before_first_request
def init_rollbar():
"""init rollbar module"""
rollbar.init(
# access token for the demo app: https://rollbar.com/demo
'fc316ac1f7404dc28af26d5baed1416c',
# environment name
'flasktest',
# server root directory, makes tracebacks prettier
root=os.path.dirname(os.path.realpath(__file__)),
# flask already sets up logging
allow_logging_basic_config=False)
# send exceptions from `app` to rollbar, using flask's signal system.
got_request_exception.connect(rollbar.contrib.flask.report_exception, 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)

Python: Making a Flask Rest API Asynchronous and Deploying it

I have a python server that is currently keeping track of the location of all the buses in my university and generating predictions of arrivals to specific locations.
Now, I wanted to attach a lightweight REST API to this server but I have been running intro problems.
I tried using flask with the following code:
from flask import Flask, jsonify
from PredictionWrapper import *
import threading
class RequestHandler():
def __init__(self,predictionWrapper):
self.app = Flask(__name__)
self.predictor = predictionWrapper
self.app.debug = False
self.app.add_url_rule('/<route>/<int:busStop>','getSinglePrediction',self.getSinglePrediction)
t = threading.Thread(target=self.app.run, kwargs={'host':'0.0.0.0', 'port':80, 'threaded':True})
t.start()
def getSinglePrediction(self, route, busStop):
# TODO Get the actual prediction with given parameters
prediction = self.predictor.getPredictionForStop(route, busStop)
return jsonify({'busStop': busStop, 'prediction': prediction})
def getStopPrediction(self, busStop):
# TODO Get the actual prediction with given parameters
return jsonify({'busStop': busStop, 'prediction': 2})
def run(self):
self.app.run(host='0.0.0.0', port=80, threaded=True)
The problem is that I have been encountering the error below after about half a day of running the server. Note that no requests were made to the server around the time it failed with the following error:
ERROR:werkzeug: - - [01/May/2016 09:55:55] code 400, message Bad request syntax ('\x02\xfd\xb1\xc5!')
After investigating I believe I need to deploy to a WSGI production server. But I have no clue what it means in this specific approach given that 1)the flask server is being threaded in order to run the rest of the prediction application, and 2)I am using classes which none of the documentation uses.
Any help on how to setup the wsgi file with apache, gunicorn, or the technology of your choice would be appreciated. Also, any comments on a better approach on making a non-blocking REST API would be helpful.
Let me know if you need any further clarification!
Not sure if this can actually solve your problem but you can use the coroutine based web server gevent. They have a WSGI server that you can use if that's what you meant by saying that you need to deploy a WSGI production server.
If you want to implement the server to your flask application just do the following:
from gevent.pywsgi import WSGIServer
app = Flask(__name__)
http_server = WSGIServer(('', 5000), app)
http_server.serve_forever()
Gevent in general is a very powerful tool and by issuing context switches as necessary it can handle multiple clients very easily. Also, gevent fully supports flask.
First thing to do would be to put exception handling to deal with bad JSON request data (which maybe is what's happening) something like
def getSinglePrediction(self, route, busStop):
try:
prediction = self.predictor.getPredictionForStop(route, busStop)
return jsonify({'busStop': busStop, 'prediction': prediction})
except:
return jsonify({'busStop': 'error', 'prediction': 'error'})

Categories