Python Flask/JSON: string indices must be integers - python

I am trying to write a webserver with Flask and e.g. have following function for post:
#app.route('/v1/tasks', methods=['POST'])
def post():
data=request.get_json()
title=data["title"]
tasks.append(json.dumps({"id": len(tasks), "title": title, "is_completed": "false"}))
index=len(tasks)-1
return json.dumps({"id":index}), 200
title=data["title"] throws following error:
TypeError: string indices must be integers
The input format for POST should be:
{title: "Test Task 2"}
I am confused because I saw a different post function, where access the contents of the JSON worked like this:
#app.route('/post', methods=['POST'])
def post():
data=request.get_json()
dictionary[data["key"]]=data["value"]
data["message"]="success"
return json.dumps(data)
What do I need to change so that I can access the title from the input JSON?
Thank you for your help!

There is nothing wrong. Verify if you are using the correct MIME media type for posting JSON, which is application/json. Test your Flask server with this code and see if it works correctly.
import requests
response = requests.post('http://localhost:5000/v1/tasks', json={'title': "Test Task 2"})
print(response.status_code)

Related

parse Hash "#" as string in URL request in flask routes

I am trying to parse "#" symbol as a direct url in Flask project. The issue is everytime the url is requested, it breaks any value that has # init as it's a special character in url encoding.
localhost:9999/match/keys?source=#123&destination=#123
In flask, I am trying to get these arguments like this
app.route(f'/match/keys/source=<string:start>/destination=<string:end>', methods=['GET'])
The url response that i see on console is this:
"GET /match/keys/source=' HTTP/1.0" 404 -] happens
I believe you might not fully understand how 'query strings' work in flask. This url:
app.route(f'/match/keys/source=<string:start>/destination=<string:end>', methods=['GET'])
won't work as you expect as it won't match the request:
localhost:9999/match/keys?source=#123&destination=#123
rather it aught to be:
#app.route('/match/keys', methods=['GET'])
and this would match:
localhost:9999/match/keys?source=%23123&destination=%23123
Then to catch those 'query strings' you do:
source = request.args.get('source') # <- name the variable what you may
destination = request.args.get('destination') # <- same as the naming format above
So when you call localhost:9999/match/keys?source=%23123&destination=%23123 you test for those 'query strings' in the request url and if they are they that route function would execute.
I wrote this test:
def test_query_string(self):
with app.test_client() as c:
rc = c.get('/match/keys?source=%23123') # <- Note use of the '%23' to represent '#'
print('Status code: {}'.format(rc.status_code))
print(rc.data)
assert rc.status_code == 200
assert 'source' in request.args
assert rc.data.decode('utf-8') == "#123"
and it passes using this route function:
#app.route('/match/keys', methods=['GET'])
def some_route():
s = request.args.get('source')
return s
So you see I was able to catch the query string source value in my unit test.
I found another trick to work around with it. Instead of using GET method, I switched to POST
localhost:9999/match/keys
and in the app.routes, i sent the argument to get_json.
app.route('/match/keys/',method=['POST'])
def my_func():
arg = request.get_json
In postman, I send the POST request and send the body to be like this:
Postman Post request

How can I return a json of files with python flask-restfull?

I'm trying to send two files in a json object with flask restfull and send_file.
When I try to access the json object on client side i get this error:
TypeError: Object of type 'Response' is not JSON serializable
This is what i do i my flask app:
class Download(Resource):
def get(self):
try:
return {"file1" : send_file('uploads/kode.png'), "file2" : send_file('uploads/kode.png')}, 200
except:
return {"message" : "File not found!"}, 404
How can I return a json of files?
If I do the same thing with only one file without wrapping the send_file() in {}, I can access that file on the front end with java script.
You mean like:
return send_file('path/to/file.png')
That works, because Flask's send_file function actually returns a Response object.
This is also valid code (as of flask version 1.1.0):
return {"message" : "File not found!"}, 404
Here you're returning a dictionary with the key 'message' and value 'File not found!'. Flask will turn this into a Response object, with a status code of 404.
That dictionary is jsonified automatically (as of flask version 1.1.0).
When you try to return this:
return {"file1" : send_file('uploads/kode.png')}, 200
The Response object returned by send_file is then jsonified, hence the exception:
TypeError: Object of type 'Response' is not JSON serializable
The obvious way to make this work is that the frontend should make a separate request to the server for each image, passing some kind of ID which the Flask route function should then obtain and use to work out the filepath, then ultimately: return sendfile(filepath).
If you really want to send several images in one JSON response, you could look at base64 encoding the image and making a data_uri string which can be JSON serialized. However unless you really need that data to be passed as JSON, the former option is probably the go-to.
I think your except is too broad. but to get a json object back, import json and its object.json()
import json
try:
return file.json()
except Exception as e:
print(e)
or your can import the json library from flask
from flask import jsonify
def myMethod():
....
response = jsonify(data)
response.status_code = 200 # or 400 or whatever
return response

Django json data in request.body but empty

I'm using an API which requires to send a callback to a url. Thus I configure my url and my view :
def get_callback(request):
...
some treatment with request.body
My view always returns that request.body contains " b'' ". However, it must contain a lot of informations, encoded in JSON.
Indeed I know that theses informations are well sent to the callback url, I tried with requestbin.in (http://requestb.in/1d4dkk01?inspect#10fl7s) and the raw body is full.
What could case the body to be empty ? Could it be the nginx configuration ? or in setting.py ?
Thanks you
I think, you should return response have kind of json data for view. Like it
import json
def get_call_back(request):
# Do something to return dictionary same as {'abc': xyz}
json_data = json.dumps(data)
return HttpResponse(json_data, content_type='application/json')

Flask: Error when trying to send a JSON file for client download

I have a dictionary that maps one key to multiple values and I want to send that dict as a json file for the user to download.
#app.route('/test', methods=['GET','POST'])
def test():
data=dict()
foods=['burger','hotdog']
foods2=['fries','chips']
data['John']=foods
data['Ken']=foods2
nutrition=jsonify(data)
return Response(nutrition,
mimetype='application/json',
headers={'Content-Disposition':'attachment;filename=nutrition.json'})
Followed solution to a similar post, however I get an error of:
TypeError: 'Response' object is not iterable
I have also tried sending data as a parameter to Response, with no avail. Can someone explain what I'm doing wrong.
jsonify creates a Response object for you, so you shouldn't pass that into a Response constructor.
nutrition = jsonify(data)
nutrition.headers['Content-Disposition'] = 'attachment;filename=nutrition.json'
return nutrition

Unit testing Django JSON View

I'm trying to write some unit tests for some Django json_view views and I'm having trouble passing the json_string to the view. I posted a related question yesterday about passing a json string to a Django view from the JS, the issue was that in my JS I was just passing the json string where I needed to be passing the string as the attribute of an object, because I was failing to do this the string was being taken as the key for the resulting query dict. I'm having a similar problem again except that this time it is form a Django unit test to the Django View. Here is a simplified version of my code which produces the same result.
class MyTestCase(TestCase):
def setUp(self):
self.u = User.objects.create_user('test','test','test')
self.u.is_active = True
self.u.save()
self.client.login(username='test',password='test')
def test_create_object_from_form(self):
"""Test the creation of the Instance from the form data."""
import json
json_string json.dumps({'resource':{'type':'book','author':'John Doe'}})
print(json_string)
response = self.client.post(reverse('ajax_view'),
{'form':json_string},'json')
self.assetNotContains(response,'error')
and the view looks like this
#json_view
def ajax_view(request):
"""Process the incoming form data."""
if request.method == 'POST':
print(request.POST)
form_data = json.loads(request.POST['form'])
resource_data = form_data['resource']
form = MyUserForm(resource_data)
if form.is_valid():
...
Here is what the two print statements produce when the test is run. The json_string is
{"resource": {"type": "book", "author": "John Doe"}}
and the query dict looks like
<QueryDict: {u'{\'form\': \'{"resource": {"type": "book", "author": "John Doe"}}\'}': [u'']}>
I'm total newbie with JS and ajax, so don't worry about hurting my pride, the answer is probably so close it could jump up and bite me.
Final edit
I originally stated that header HTTP_X_REQUESTED_WITH='XMLHttpRequest' was necessary in the post call but this is currently false while in tests. This header is necessary for the csrf middleware but csrf is disabled in tests. However, I still believe it is a good practice to put in test even if middleware disables csrf since most javascript library already pass this header by default when doing ajax. Also, if another piece of code that is not disabled ever use the is_ajax method, you won't need to debug your unittest for hours to figure out that the header was missing.
The problem is with the content-type because when django gets a value in there that is different than text/html, it doesn't use the default post data handling which is to format your data like in a query: type=book&author=JohnDoe for example.
Then the fixed code is:
response = self.client.post(reverse('ajax_view'),
{'form':json_string},
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
Here's how I'm using it myself:
post_data = {
"jsonrpc" : "2.0", "method": method, "params" : params, "id" : id }
return client.post('/api/json/',
json.dumps(post_data), "text/json",
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
to do some json-rpc. Notice that since I pass a different content-type than the default value, my data is passed as is in the post request.
Thank you to #Eric_Fortin for turning me on to the header, it does not however resolve my issue with the malformed query dictionary using 'client.post'. Once I made the change from POST to GET with the XMLHttpRequest header my query dictionary straitened itself out. Here is the current solution:
response = self.client.get(reverse('ajax_view'),
{'form':json_string},'json',
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
this is only a partial answer since this request is going to change data on the server and should be POST not a GET.
Edit:
Here is the final code in my test that works for passing a JSON string via POST to my view:
response = self.client.post(reverse('ajax_view'),
{'form':json.dumps(json_dict)})
Now printing from the view shows that the query dictionary is well formed.
<QueryDict: {u'form': [u'{"resource": {"status": "reviewed", "name": "Resource Test", "description": "Unit Test"}}']}>
I found the answer while tinkering with one of my co-workers, removing the content_type 'json' fixed the malformed query dictionary. The view that is being tested does not make use of or call the 'HttpRequest.is_ajax()', sending the header XMLHttpRequest' has no impact on my issue, though including the header would constitute good-form since this post is an ajax request.

Categories