I am writing a Flask unit test for a function that would return a render template. I tried few ways but it seems not working.
Here is the function:
#app.route('/', methods=['POST'])
#lti(request='initial', error=error, app=app)
def chooser(lti=lti):
return_url = request.form.get('launch_presentation_return_url', '#')
return render_template(
'chooser.html'
)
Few ways that I have been trying:
# 1st way
rv = self.app.post('/')
self.assertTrue('Choose an Icon to Insert' in rv.get_data(as_text=True))
# Error
self.assertTrue('Choose an Icon to Insert' in rv.get_data(as_text=True))
AssertionError: False is not true
# 2nd way
rv = self.app.post('/chooser.html')
assert '<h1>Choose an Icon to Insert</h1>' in rv.data
# Error
assert 'Choose an Icon to Insert' in rv.data
AssertionError
chooser.html
<body>
<h1>Choose an Icon to Insert</h1>
</body>
Thanks for all your helps.
Here an example which can help you to understand. Our application - app.py:
import httplib
import json
from flask import Flask, request, Response
app = Flask(__name__)
#app.route('/', methods=['POST'])
def main():
url = request.form.get('return_url')
# just example. will return value of sent return_url
return Response(
response=json.dumps({'return_url': url}),
status=httplib.OK,
mimetype='application/json'
)
Our tests - test_api.py:
import json
import unittest
from app import app
# set our application to testing mode
app.testing = True
class TestApi(unittest.TestCase):
def test_main(self):
with app.test_client() as client:
# send data as POST form to endpoint
sent = {'return_url': 'my_test_url'}
result = client.post(
'/',
data=sent
)
# check result from server with expected data
self.assertEqual(
result.data,
json.dumps(sent)
)
How to run:
python -m unittest discover -p path_to_test_api.py
Result:
----------------------------------------------------------------------
Ran 1 test in 0.009s
OK
Hope it helps.
Related
I am trying to use Dialogflow in my Flask app but I am getting 403 IAM permission 'dialogflow.sessions.detectIntent' I have checked twice my role is project owner in cloud console. I am following this tutorial.
This is my app.py
from flask import Flask
from flask import request, jsonify
import os
import dialogflow
from google.api_core.exceptions import InvalidArgument
import requests
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = 'service_key.json'
DIALOGFLOW_PROJECT_ID = 'whatsappbotagent-gmsl'
DIALOGFLOW_LANGUAGE_CODE = 'en'
SESSION_ID = 'me'
app = Flask(__name__)
app.config["DEBUG"] = True
#app.route('/')
def root():
return "Hello World"
#app.route('/api/getMessage', methods = ['POST'])
def home():
message = request.form.get('Body')
mobnu = request.form.get('From')
session_client = dialogflow.SessionsClient()
session = session_client.session_path(DIALOGFLOW_PROJECT_ID, SESSION_ID)
text_input = dialogflow.types.TextInput(text = message, language_code = DIALOGFLOW_LANGUAGE_CODE)
query_input = dialogflow.types.QueryInput(text = text_input)
try:
response = session_client.detect_intent(session = session, query_input = query_input)
except InvalidArgument:
raise
print("Query text: ", response.query_result.query_text)
# sendMessage()
return response.query_result.fullfilment_text
if __name__ == '__main__':
app.run()
OK, so after a lot of searching and trial and error I found the issue.
Actually while creating a service account the user gets a service email and that email will have access to use the project but I was not adding that email in project permission emails. Adding that email in IAM & Admin > IAM > +ADD will make it work.
Maybe there was a better solution but this is what works for me and hopefully solves others problem if have similar issue like this.
I am working with Flask Socketio. My code right now console logs every time a user opens a page. However when I open the page in a new tab/window the console of the original user in not updated. Below is the code, have a look at it
let socket = io.connect("http://127.0.0.1:5000/")
socket.on("connect",() => {
socket.emit("my custom event",{text:"I have joined"})
})
socket.on("my response",function(msg) {
console.log(msg)
})
And here is the python code for flask
from flask import Flask, render_template, request
import requests
from flask_socketio import SocketIO, emit, send
app = Flask(__name__)
app.config["SECRET_KEY"] = "hope"
socketio = SocketIO(app)
#app.route('/')
def hello_world():
return render_template("index.html")
#app.route('/1')
def random_route():
return render_template("index2.html")
#socketio.on('message')
def message(data):
print(data)
#socketio.on('my custom event')
def handle_custom_event(data):
emit("my response", data)
if __name__ == "__main__":
socketio.run(app, debug=True)
The default for the emit function is to address the event only to the sender. If you want to address all your connected clients, you have to indicate so with the broadcast option:
#socketio.on('my custom event')
def handle_custom_event(data):
emit("my response", data, broadcast=True)
How to call my microservice-2 from microservice-1. So our result looks like this:-
Result :- {“message”: “vivek”} --> {“message”: “keviv”, “random”: 3.89}
command to access microservice-1:-
curl http://127.0.0.1:5000/reverse_random/vivek
microservice-1
from flask import Flask, jsonify
app = Flask(__name__)
#app.route('/reverse_reandom/<string:string>', methods=['GET'])
def reverse(string):
string = string[::-1]
return jsonify({'message': string })
if __name__ == '__main__':
app.run(debug = True)
microservice-2
import random
from flask import Flask, jsonify
app = Flask(__name__)
#app.route('/', methods=['GET'])
def myRandom():
r1 = random.uniform(0, 10)
return jsonify({'message': r1 })
if __name__ == '__main__':
app.run(debug=True)
you'll need to issue a GET request to service 2 in order to get the random number, I suggest to use requests for this, like
r = requests.get('url-for-service-2:port/')
data = r.json()
random_num = data['message']
keep in mind to check the data object for message key, or using .get() or equivalent
Run microservice-2 on a different port. Send request using Python standard or 3rd party library from microservice-1
to microservice-2 upon request to microservice-1.
Below is the example of using Python3 standard library only:
m1.py:
from flask import Flask, jsonify
import urllib
import json
app = Flask(__name__)
#app.route('/reverse_random/<string:string>', methods=['GET'])
def reverse(string):
content = urllib.request.urlopen('http://127.0.0.1:5001').read().decode('utf-8')
print('response from m2: ', content)
string = string[::-1]
return jsonify({'message': string, 'random' : json.loads(content)['message']})
if __name__ == '__main__':
app.run(debug = True)
m2.py:
import random
from flask import Flask, jsonify
app = Flask(__name__)
#app.route('/', methods=['GET'])
def myRandom():
r1 = random.uniform(0, 10)
return jsonify({'message': r1 })
if __name__ == '__main__':
app.run(debug=True, port=5001) # running m2 on a different port than default 5000
Run the m1: python3 m1.py
Run the m2 in a different shell: python3 m2.py
Send request to m1: curl http://127.0.0.1:5000/reverse_random/vivek
The result is:
{
"message": "keviv",
"random": 4.138115905045612
}
Observe the log of m1 and of m2 to make sure m2 was invoked.
To connect between services you can use background tasks such as celery and ramq or use nsq and nats
I have created a simple flask app that and I'm reading the response from python as:
response = requests.post(url,data=json.dumps(data), headers=headers )
data = json.loads(response.text)
Now my issue is that under certain conditions I want to return a 400 or 500 message response. So far I'm doing it like this:
abort(400, 'Record not found')
#or
abort(500, 'Some error...')
This does print the message on the terminal:
But in the API response I kept getting a 500 error response:
The structure of the code is as follows:
|--my_app
|--server.py
|--main.py
|--swagger.yml
Where server.py has this code:
from flask import render_template
import connexion
# Create the application instance
app = connexion.App(__name__, specification_dir="./")
# read the swagger.yml file to configure the endpoints
app.add_api("swagger.yml")
# Create a URL route in our application for "/"
#app.route("/")
def home():
"""
This function just responds to the browser URL
localhost:5000/
:return: the rendered template "home.html"
"""
return render_template("home.html")
if __name__ == "__main__":
app.run(host="0.0.0.0", port="33")
And main.py has all the function I'm using for the API endpoints.
E.G:
def my_funct():
abort(400, 'Record not found')
When my_funct is called, I get the Record not found printed on the terminal, but not in the response from the API itself, where I always get the 500 message error.
You have a variety of options:
The most basic:
#app.route('/')
def index():
return "Record not found", 400
If you want to access the headers, you can grab the response object:
#app.route('/')
def index():
resp = make_response("Record not found", 400)
resp.headers['X-Something'] = 'A value'
return resp
Or you can make it more explicit, and not just return a number, but return a status code object
from flask_api import status
#app.route('/')
def index():
return "Record not found", status.HTTP_400_BAD_REQUEST
Further reading:
You can read more about the first two here: About Responses (Flask quickstart)
And the third here: Status codes (Flask API Guide)
I like to use the flask.Response class:
from flask import Response
#app.route("/")
def index():
return Response(
"The response body goes here",
status=400,
)
flask.abort is a wrapper around werkzeug.exceptions.abort which is really just a helper method to make it easier to raise HTTP exceptions. That's fine in most cases, but for restful APIs, I think it may be better to be explicit with return responses.
Here's some snippets from a Flask app I wrote years ago. It has an example of a 400 response
import werkzeug
from flask import Flask, Response, json
from flask_restplus import reqparse, Api, Resource, abort
from flask_restful import request
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
api = Api(app)
parser = reqparse.RequestParser()
parser.add_argument('address_to_score', type=werkzeug.datastructures.FileStorage, location='files')
class MissingColumnException(Exception):
pass
class InvalidDateFormatException(Exception):
pass
#api.route('/project')
class Project(Resource):
#api.expect(parser)
#api.response(200, 'Success')
#api.response(400, 'Validation Error')
def post(self):
"""
Takes in an excel file of addresses and outputs a JSON with scores and rankings.
"""
try:
df, input_trees, needed_zones = data.parse_incoming_file(request)
except MissingColumnException as e:
abort(400, 'Excel File Missing Mandatory Column(s):', columns=str(e))
except Exception as e:
abort(400, str(e))
project_trees = data.load_needed_trees(needed_zones, settings['directories']['current_tree_folder'])
df = data.multiprocess_query(df, input_trees, project_trees)
df = data.score_locations(df)
df = data.rank_locations(df)
df = data.replace_null(df)
output_file = df.to_dict('index')
resp = Response(json.dumps(output_file), mimetype='application/json')
resp.status_code = 200
return resp
#api.route('/project/health')
class ProjectHealth(Resource):
#api.response(200, 'Success')
def get(self):
"""
Returns the status of the server if it's still running.
"""
resp = Response(json.dumps('OK'), mimetype='application/json')
resp.status_code = 200
return resp
You can return a tuple with the second element being the status (either 400 or 500).
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello():
return "Record not found", 400
if __name__ == '__main__':
app.run()
Example of calling the API from python:
import requests
response = requests.get('http://127.0.0.1:5000/')
response.text
# 'This is a bad request!'
response.status_code
# 400
I think you're using the abort() function correctly. I suspect the issue here is that an error handler is that is catching the 400 error and then erroring out which causes the 500 error. See here for more info on flask error handling.
As an example, the following would change a 400 into a 500 error:
#app.errorhandler(400)
def handle_400_error(e):
raise Exception("Unhandled Exception")
If you're not doing any error handling, it could be coming from the connexion framework, although I'm not familiar with this framework.
You can simply use #app.errorhandler decorator.
example:
#app.errorhandler(400)
def your_function():
return 'your custom text', 400
I am learning how to test functions in Python using Mock. I am trying to write a test for a simple function,
#social.route('/friends', methods=['GET', 'POST'])
def friends():
test_val = session.get('uid')
if test_val is None:
return redirect(url_for('social.index'))
else:
return render_template("/home.html")
However, I am stuck at how to try and mock session.get('uid') value. So far, this has been my attempt,
#patch('session', return_value='')
#patch('flask.templating._render', return_value='')
def test_mocked_render(self, mocked, mock_session):
print "mocked", repr(self.app.get('/social/friends').data)
print "was _render called?", mocked.called
This attempt may be completely wrong and this is definitely the wrong way as I am still not able to mock session. However, can someone please guide me in the right way through this? Thanks.
Starting with Flask 0.8 we provide a so called “session transaction” which simulates the appropriate calls to open a session in the context of the test client and to modify it.
Let's give a simple example: app.py
from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'very secret'
#app.route('/friends', methods=['GET', 'POST'])
def friends():
test_val = session.get('uid')
if test_val is None:
return 'uid not in session'
else:
return 'uid in session'
if __name__ == '__main__':
app.run(debug=True)
The test file: test_app.py
import unittest
from app import app
class TestSession(unittest.TestCase):
def test_mocked_session(self):
with app.test_client() as c:
with c.session_transaction() as sess:
sess['uid'] = 'Bar'
# once this is reached the session was stored
rv = c.get('/friends')
assert rv.data == 'uid in session'
if __name__ == '__main__':
unittest.main()
run the tests via python test_app.py.
Documentation: Accessing and Modifying Sessions