How to assert JSON data using nose2? [duplicate] - python

How can I test that the response a Flask view generated is JSON?
from flask import jsonify
#app.route('/')
def index():
return jsonify(message='hello world')
c = app.app.test_client()
assert c.get('/').status_code == 200
# assert is json

As of Flask 1.0, response.get_json() will parse the response data as JSON or raise an error.
response = c.get("/")
assert response.get_json()["message"] == "hello world"
jsonify sets the content type to application/json. Additionally, you can try parsing the response data as JSON. If it fails to parse, your test will fail.
from flask import json
assert response.content_type == 'application/json'
data = json.loads(response.get_data(as_text=True))
assert data['message'] == 'hello world'
Typically, this test on its own doesn't make sense. You know it's JSON since jsonify returned without error, and jsonify is already tested by Flask. If it was not valid JSON, you would have received an error while serializing the data.

There is a python-library for it.
import json
#...
def checkJson(s):
try:
json.decode(s)
return True
except json.JSONDecodeError:
return False
If you also want to check if it is a valid string, check the boundaries for "s.
You can read the help here on pythons website https://docs.python.org/3.5/library/json.html .

Related

How to return 400 (Bad Request) on Flask?

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

Retrieve data from url into json format in flask

I am trying to retrieve data from url into json format using flask-restplus.
from flask import Flask, render_template
import requests
import json
from flask_restplus import Resource, Api, fields
from flask_sqlalchemy import SQLAlchemy
# config details
COLLECTION = 'indicators'
app = Flask(__name__)
api = Api(
app,
title='Akhil Jain',
description='Developing ' \
'a Flask-Restplus data service that allows a client to ' \
'read and store some publicly available economic indicator ' \
'data for countries around the world, and allow the consumers ' \
'to access the data through a REST API.'
)
#api.route('/indicators')
def get(self):
uri = 'http://api.worldbank.org/v2/indicators'
try:
res = requests.get(uri)
print(res)
return res.json()
except:
return False
if __name__ == '__main__':
app.run(debug=True)
but after trying out the GET, the response i am getting is False instead of the json data.
Could anyone tell me how to get the response data so that i can process it to sqllite db.
Thanks
you have to get the content from response
If you would want to save the xml data then.
try:
res = requests.get(uri)
print(res.content)
return res.content
except:
return False
If you want to save it as json, then install module xmltodict.
try:
res = requests.get(uri)
jsondata = xmltodict.parse(res.content)
print(jsondata)
return jsondata
except:
return False
You're trying to retrieve an XML producing endpoint. - Invoking json will raise an exception since the content retrieved is not json.
The ideal way of doing this is to build a function that parses the data as per your requirements; the example below can be treated as a reference.
import xml.etree.ElementTree as element_tree
xml = element_tree.fromstring(response.content)
for entry in xml.findall(path_here, namespace_here):
my_value = entry.find(attribute_here, namespace_here).text
You can opt to use lxml or other tools available. - The above is an example of how you can parse your xml response.

Slack Interactive Messages: POST request payload has an unexpected format

I'm getting a POST request inside a Flask app from Slack. The request is sent when a user presses on an interactive message button. According to Slack docs I must extract the body of the request to verify the signature.
My computed signature doesn't match the one sent by Slack, though.
In fact, the body of the request comes as some encoded string. The string is actually an encoded dictionary instead of a query str parameters, as expected.
Here's the beginning of my view:
#app.route('/register', methods=['POST'])
def register_visit():
data = request.get_data()
signature = request.headers.get('X-Slack-Signature', None)
timestamp = request.headers.get('X-Slack-Request-Timestamp', None)
signing_secret = b'aaaaaaaaaaaaaaaa'
# old message, ignore
if round(actual_time.time() - float(timestamp)) > 60 * 5:
return
concatenated = ("v0:%s:%s" % (timestamp, data)).encode('utf-8')
computed_signature = 'v0=' + hmac.new(signing_secret, msg=concatenated, digestmod=hashlib.sha256).hexdigest()
if hmac.compare_digest(computed_signature, signature):
...
I've tried to format the received data to make it look like:
token=fdjkgjl&user_id=1234... but I am not aware of all of the necessary parameters that have to be present in the data.
Any ideas are highly appreciated.
The body of the message is following - after being URL decoded (note I've modified possibly sensitive data):
b'payload={"type":"interactive_message","actions":
[{"name":"yes_button","type":"button","value":"236"}],"callback_id":"visit_button","team":{"id":"fffff","domain":"ffff"},"channel":{"id":"ffff","name":"directmessage"},"user":{"id":"ffffff","name":"fffft"},"action_ts":"1540403943.419120","message_ts":"1541403949.000100","attachment_id":"1","token":"8LpjBuv13J7xAjhl2lEajoBU","is_app_unfurl":false,"original_message":{"text":"Test","bot_id":"DDDDDDDDD","attachments":[{"callback_id":"visit_button","text":"Register","id":1,"color":"3AA3E3","actions":[{"id":"1","name":"yes_button","text":"Yes","type":"button","value":"236","style":""}],"fallback":"Register"}],"type":"message","subtype":"bot_message","ts":"1540413949.000100"},"response_url":"https://hooks.slack.com/actions/ffffff/ffffff/tXJjx1XInaUhrikj6oEzK08e","trigger_id":"464662548327.425084163429.dda35a299eedb940ab98dbb9386b56f0"}'
The reason you are getting the "garbled" data is that you are using request.get_data(). That method will return the raw data of a request, but not do any decoding for you.
Much more convenient is to use request.form.get('payload'), which will directly give you the JSON string of the request object. You can then convert that into a dict object with json.loads() to process it further in your app.
Note that the format you received is the correct format for interactive messages. You will not get a query string (e.g. "token=abc;user_id?def...") as you suggested (like for slash command requests). Interactive message request will always contain the request as JSON string in a payload form property. See here for reference.
Here is a simple working example, which will reply a greeting to the user that pressed the button. It will work directly with Slack, but I recommend using Postman to test it.
#app.py
from flask import Flask, request #import main Flask class and request object
import json
app = Flask(__name__) #create the Flask app
#app.route('/register', methods=['POST'])
def register_visit():
slack_req = json.loads(request.form.get('payload'))
response = '{"text": "Hi, <#' + slack_req["user"]["id"] + '>"}'
return response, 200, {'content-type': 'application/json'}
if __name__ == '__main__':
app.run(debug=True, port=5000) #run app in debug mode on port 5000
OK, the issue wasn't related to how Slack sends me the message. It was about misunderstanding which data comes as bytes and which data is unicode. The culprit was string formatting in my case - the line concatenated = ("v0:%s:%s" % (timestamp, data)).encode('utf-8') should have been concatenated = (b"v0:%b:%b" % (timestamp.encode("utf-8"), data)). Data is already bytes, timestamp meanwhile is unicode.
Cannot believe I've banged my head on this for hours -_-
#app.route('/register', methods=['POST'])
def register_visit():
data = request.get_data()
signature = request.headers.get('X-Slack-Signature', None)
timestamp = request.headers.get('X-Slack-Request-Timestamp', None)
signing_secret = b'aaaaaaaaaaaaaaaa'
# old message, ignore
if round(actual_time.time() - float(timestamp)) > 60 * 5:
return
concatenated = (b"v0:%b:%b" % (timestamp.encode("utf-8"), data))
computed_signature = 'v0=' + hmac.new(signing_secret, msg=concatenated,
digestmod=hashlib.sha256).hexdigest()
if hmac.compare_digest(computed_signature, signature):
...
This worked for me
from urllib import parse
parsed_text = parse.unquote('your bytes text here')

Catch http-status code in Flask

I lately started using Flask in one of my projects to provide data via a simple route. So far I return a json file containing the data and some other information. When running my Flask app I see the status code of this request in terminal. I would like to return the status code as a part of my final json file. Is it possible to catch the same code I see in terminal?
Some simple might look like this
from flask import Flask
from flask import jsonify
app = Flask(__name__)
#app.route('/test/<int1>/<int2>/')
def test(int1,int2):
int_sum = int1 + int2
return jsonify({"result":int_sum})
if __name__ == '__main__':
app.run(port=8082)
And in terminal I get:
You are who set the response code (by default 200 on success response), you can't catch this value before the response is emited. But if you know the result of your operation you can put it on the final json.
#app.route('/test/<int1>/<int2>/')
def test(int1, int2):
int_sum = int1 + int2
response_data = {
"result": int_sum,
"sucess": True,
"status_code": 200
}
# make sure the status_code on your json and on the return match.
return jsonify(response_data), 200 # <- the status_code displayed code on console
By the way if you access this endpoint from a request library, on the response object you can find the status_code and all the http refered data plus the json you need.
Python requests library example
import requests
req = requests.get('your.domain/test/3/3')
print req.url # your.domain/test/3/3
print req.status_code # 200
print req.json() # {u'result': 6, u'status_code: 200, u'success': True}
You can send HTTP status code as follow:
#app.route('/test')
def test():
status_code = 200
return jsonify({'name': 'Nabin Khadka'}, status_code) # Notice second element of the return tuple(return)
This way you can control what status code to return to the client (typically to web browser.)

object is not JSON serializable

I'm having some trouble with Mongodb and Python (Flask).
I have this api.py file, and I want all requests and responses to be in JSON, so I implement as such.
#
# Imports
#
from datetime import datetime
from flask import Flask
from flask import g
from flask import jsonify
from flask import json
from flask import request
from flask import url_for
from flask import redirect
from flask import render_template
from flask import make_response
import pymongo
from pymongo import Connection
from bson import BSON
from bson import json_util
#
# App Create
#
app = Flask(__name__)
app.config.from_object(__name__)
#
# Database
#
# connect
connection = Connection()
db = connection['storage']
units = db['storage']
#
# Request Mixins
#
#app.before_request
def before_request():
#before
return
#app.teardown_request
def teardown_request(exception):
#after
return
#
# Functions
#
def isInt(n):
try:
num = int(n)
return True
except ValueError:
return False
def isFloat(n):
try:
num = float(n)
return True
except ValueError:
return False
def jd(obj):
return json.dumps(obj, default=json_util.default)
def jl(obj):
return json.loads(obj, object_hook=json_util.object_hook)
#
# Response
#
def response(data={}, code=200):
resp = {
"code" : code,
"data" : data
}
response = make_response(jd(resp))
response.headers['Status Code'] = resp['code']
response.headers['Content-Type'] = "application/json"
return response
#
# REST API calls
#
# index
#app.route('/')
def index():
return response()
# search
#app.route('/search', methods=['POST'])
def search():
return response()
# add
#app.route('/add', methods=['POST'])
def add():
unit = request.json
_id = units.save(unit)
return response(_id)
# get
#app.route('/show', methods=['GET'])
def show():
import pdb; pdb.set_trace();
return response(db.units.find())
#
# Error handing
#
#app.errorhandler(404)
def page_not_found(error):
return response({},404)
#
# Run it!
#
if __name__ == '__main__':
app.debug = True
app.run()
The problem here is json encoding data coming to and from mongo. It seems I've been able to "hack" the add route by passing the request.json as the dictionary for save, so thats good... the problem is /show. This code does not work... When I do some logging I get
TypeError: <pymongo.cursor.Cursor object at 0x109bda150> is not JSON serializable
Any ideas? I also welcome any suggestions on the rest of the code, but the JSON is killing me.
Thanks in advance!
While #ErenGüven shows you a nice manual approach to solving this json serializing issue, pymongo comes with a utility to accomplish this for you. I use this in my own django mongodb project:
import json
from bson import json_util
json_docs = []
for doc in cursor:
json_doc = json.dumps(doc, default=json_util.default)
json_docs.append(json_doc)
Or simply:
json_docs = [json.dumps(doc, default=json_util.default) for doc in cursor]
And to get them back from json again:
docs = [json.loads(j_doc, object_hook=json_util.object_hook) for j_doc in json_docs]
The helper utilities tell json how to handle the custom mongodb objects.
When you pass db.units.find() to response you pass a pymongo.cursor.Cursor object to json.dumps ... and json.dumps doesn't know how to serialize it to JSON. Try getting the actual objects by iterating over the cursor to get its results:
[doc for doc in db.units.find()]
import json
from bson import json_util
docs_list = list(db.units.find())
return json.dumps(docs_list, default=json_util.default)
To encode MongoDB documents to JSON, I use a similar approach to the one below which covers bson.objectid.ObjectId and datetime.datetime types.
class CustomEncoder(json.JSONEncoder):
"""A C{json.JSONEncoder} subclass to encode documents that have fields of
type C{bson.objectid.ObjectId}, C{datetime.datetime}
"""
def default(self, obj):
if isinstance(obj, bson.objectid.ObjectId):
return str(obj)
elif isinstance(obj, datetime.datetime):
return obj.isoformat()
return json.JSONEncoder.default(self, obj)
enc = CustomEncoder()
enc.encode(doc)
As for the Cursor, you need to iterate it and get documents first.
Short answer: Its a cursor object. Iterate over it , and you get python dicts. Again to serialise do this:
import json
from bson import json_util
let's say this is my query:
details = mongo.db.details.find()
# this is cursor object
#iterate over to get a list of dicts
details_dicts = [doc for doc in details]
#serialize to json string
details_json_string = json.dumps(details_dicts,default=json_util.default)
If u wish to return the above , it will be just a string. Do this to return it as usable dict or json
return json.loads(details_json_string)
#return jsonified version rather than string without unwanted "\" in earlier json string!
Hope that helps! Happy Coding!
Yea plain old jsonify works, if your query doesn't contain some complex filed like Objectid , etc!
Just in case you are looking for a more plain python way of sending the response then create an empty list and append the values inside it and finally return this list in JSON format.
from flask import Flask, jsonify
#app.route('/view', methods=["GET'])
def viewFun():
data = units.find()
s = []
for i in data:
s.append(str(i))
return jsonify(s)
May be not the perfect one but it works!
if the document id is of no use, you can simply exclude it by
objects = collection.find({}, {'_id': False})
after that simply convert it to list of dictionaries by
list(objects)

Categories

Resources