Python Flask post and return json objects - python

Apologies if this seems rudimental as I am new to Python. The task I am trying to complete is to send a json object from an iPhone app to a python script that will process a stripe payment. The problem I have is I cannot figure out how to get Python to recognise the incoming json object to extract data from it and pass onto Stripe.
I have taken a step back to simplify the problem. I have a python script that attempts to post a json object with four value pairs to another function that should extract the values, create a new json object and return that object. I cannot get it to work and any help would be greatly appreciated as I've been stuck on this for a while. I am using Flask:
`
import json
import stripe
import smtplib
import requests
from flask import Flask, request, jsonify
#application.route('/run_post')
def run_post():
url = 'http://xxx.pythonanywhere.com/stripetest'
data = {'stripeAmount': '199', 'stripeCurrency': 'USD', 'stripeToken': '122', 'stripeDescription': 'Test post'}
headers = {'Content-Type' : 'application/json'}
r = requests.post(url, data, headers=headers)
#return json.dumps(r.json(), indent=4)
return r.text
#application.route('/stripetest', methods=["POST"])
def stripeTest():
if request.method == "POST":
json_dict = json.loads(request.body.raw)
stripeAmount = json_dict['stripeAmount']
stripeCurrency = json_dict['stripeCurrency']
stripeToken = json_dict['stripeToken']
stripeDescription = json_dict['stripeDescription']
data = "{'stripeAmountRet': " + stripeAmount + ", 'stripeCurrencyRet': " + stripeCurrency + ", 'stripeTokenRet': " + stripeToken + ", 'stripeDescriptionRet': " + stripeDescription + "}"
return jsonify(data)
else:
return """<html><body>
Something went horribly wrong
</body></html>"""
`
I get the following returned in the error log when I run this:
`
2015-03-19 21:07:47,148 :Starting new HTTP connection (1): xxx.pythonanywhere.com
2015-03-19 21:07:47,151 :Exception on /stripetest [POST]
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1687, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1360, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1358, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1344, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/var/www/xxx_pythonanywhere_com_wsgi.py", line 156, in stripeTest
json_dict = json.loads(request.body.raw)
File "/usr/local/lib/python2.7/dist-packages/werkzeug/local.py", line 336, in __getattr__
return getattr(self._get_current_object(), name)
AttributeError: 'Request' object has no attribute 'body'
`

You have a couple of issues with the code. First of all, you need to properly define the json data when you make the request from the requests library. You can do this as follows:
#application.route('/run_post')
def run_post():
url = 'http://xxx.pythonanywhere.com/stripetest'
data = {'stripeAmount': '199', 'stripeCurrency': 'USD', 'stripeToken': '122', 'stripeDescription': 'Test post'}
headers = {'Content-Type' : 'application/json'}
r = requests.post(url, data=json.dumps(data), headers=headers)
#return json.dumps(r.json(), indent=4)
return r.text
Notice that we call json.dumps instead of just passing the data directly. Otherwise, the incoming request is not interpreted as json data.
Next, in your receiving function, we change it as follows:
#application.route('/stripetest', methods=["POST"])
def stripeTest():
if request.method == "POST":
json_dict = request.get_json()
stripeAmount = json_dict['stripeAmount']
stripeCurrency = json_dict['stripeCurrency']
stripeToken = json_dict['stripeToken']
stripeDescription = json_dict['stripeDescription']
data = {'stripeAmountRet': stripeAmount, 'stripeCurrencyRet': stripeCurrency, 'stripeTokenRet': stripeToken, 'stripeDescriptionRet': stripeDescription}
return jsonify(data)
else:
return """<html><body>
Something went horribly wrong
</body></html>"""
A couple of things are changed. First, we read in the data by calling request.get_json(), which properly parses the incoming json data. Note from above that we needed to change how we actually made the request for it to parse the data properly. The next issue was how you returned the data. To properly jsonify the data to return, we put the data into a python dictionary rather than into a string.
If you're calling your function to process the stripe payment from somewhere else (i.e. not using the python requests library), another issue is that you may not be defining the json request properly for Flask to later interpret. If the issue still persists after making the above change to the processing function, post how you're making the json request elsewhere and I can take a look.
Let me know if this fixes your problems!

You should check the document Flask requests
It does not define a body, instead you should try with
request.get_json()
You just need to make sure you are specifying the correct mimetype which would be "application/json".
See request.get_json() method for more info

Related

Writing API get request output to file only writes the status code

I'm trying to write the output of an API 'get' request to a file. I know the function works as the output of the request is returned successfully and in full in shell.
The function creates the file, but the file only includes the status code for the get request - can anyone help me understand why? Still pretty new to Python and very new to calling APIs...
def user_info(token):
heads = {"Authorization": "bearer " + token, "Content-type": "application/json"}
url = "xxxxx"
request = req.get(url, headers=heads)
with open('userdata.txt', 'w') as f:
f.write(str(request))
if request.status_code == 200:
return request.json()
else:
return "User Info FAILED! Failure code: {}, error: {}".format(request.status_code,request.json()['error'])
The file just contains:
<Response [200]>
Thanks!
A Response object in the requests module when called as a string will return its status code as detailed in the Response python code
def __repr__(self):
return '<Response [%s]>' % (self.status_code)
If you want to write the json from the response as a string then you can use the built in json module and use its dumps method to dump it as a string not a dict.
import requests
import json
with open("myfile.txt", "w") as output:
resp = requests.get("https://jsonplaceholder.typicode.com/todos/1")
output.write(json.dumps(resp.json()))

Python Flask : Returned file is not readable

While implementing a rest API in python flask, I have used several options to return a file (any type) , read it and save it to local repository of request but encountered with multiple errors as below:
Case 1:
def download_file():
return send_file('any_file.pdf')
r = requests.get(url = 'http://localhost:5000/download').read()
has responded with a error Response object has no attribute read/text/content
Case 2:
def download_file():
file = open('any_file.pdf','r').read()
return file
r = requests.get(url = 'http://localhost:5000/download')
has responded with a error Return doesn't accept this
So How can I do this as flask is not allowing to return a file without response object and response object is not readable and doesn't support to save that file directly.
The Flask server code in Case 1 is correct. A more complete example:
#app.route('/download')
def download_file():
# Some logic here
send_file('any_file.pdf')
However the Response object returned by requests.get doesn't have a read method. The correct way is to use:
Response.content: Content of the response, in bytes.
So, the client code should be:
r = requests.get('http://localhost:5000/download')
bytes = r.content
# Now do something with bytes, for example save it:
with open('downloaded_file.ext', 'wb') as f:
f.write(bytes)

Django - post InMemoryUploadedFile to external REST api

In the Django Rest Framework I would like to post a file, received as an InMemoryUploadedFile, to a different server as soon as it is received.
It sounds simple, but the request.post() function does not seem to properly send over such a file :
def post(self, request, *args, **kwargs):
data = request.data
print(data)
# <QueryDict: {'file': [<InMemoryUploadedFile: myfile.pdf (application/pdf)>]}>
endpoint = OTHER_API_URL + "/endpoint"
r = requests.post(endpoint, files=data)
My other server receives the request (through flask) with the name of the file, but not the content:
#app.route("/endpoint", methods=["POST"])
def endpoint():
if flask.request.method == "POST":
# I removed the many checks to simplify the code
file = flask.request.files['file']
path = os.path.join(UPLOAD_FOLDER, file.filename)
file.save(path)
print(file) #<FileStorage: u'file.pdf' (None)>
print(os.path.getsize(path)) #0
return [{"response":"ok"}]
When posting a file directly to that api in form-data with postman, It works as expected:
print(file) # <FileStorage: u'file.pdf' ('application/pdf')>
print(os.path.getsize(path)) #8541
Any help on how to fix this, i.e. transform the InMemoryUploadedFile type in something a normal REST api can understand? Or maybe just adding the right headers?
I had to figure this issue out passing an uploaded file from a Django front end website to a Django backend API in Python 3. The InMemoryUploadedFile's actual file data can be accessed via the object's .file property's .getvalue() method.
path="path/to/api"
in_memory_uploaded_file = request.FILES['my_file']
io_file = in_memory_uploaded_file.file
file_value = io_file.getvalue()
files = {'my_file': file_value}
make_http_request(path, files=files)
and can be shortened
file = request.FILES['my_file'].file.getvalue()
files = {'my_file': file}
Before this, trying to send InMemoryUploadFile objects, the file property, or the result of the read() method all proved to send a blank/empty file by the time it got to the API.
I had the same problem and the same case.
My working solution
headers = {
"Host": API_HOST,
"cache-control": "no-cache",
}
try:
data = json_request = request.POST['json_request'].strip()
data = json.loads(data) # important!
except:
raise Http404
try:
in_memory_uploaded_file = request.FILES['file'].file.getvalue()
files = {'photo': in_memory_uploaded_file} # important!
except:
files = {}
if USE_HTTPS:
API_HOST = f'https://{API_HOST}'
else:
API_HOST = f'http://{API_HOST}'
if authorization_key and len(authorization_key) > 0:
response = requests.post(f'{API_HOST}/api/json/?authorization_key={authorization_key}', headers=headers, data=data, files=files)
else:
response = requests.post(f'{API_HOST}/api/json/', headers=headers, data=data)
json_response = json.dumps(response.json())

Diagnosing AttributeError in Django view

In a Django project of mine, I've written simple webhook logic that pings a url for certain payload if some condition is met. Here's the code:
#csrf_exempt
def webhook_event(request,*args,**kwargs):
if request.method == 'POST':
data = json.loads(request.body)
event_type = data['event']
# use CDN URL from webhook payload
if event_type == 'project.datafile_updated':
url = data['data']['cdn_url']
config_manager.set_obj(url)
return json.dumps({'success':True}), 200, {'ContentType':'application/json'}
return json.dumps({'success':False}), 400, {'ContentType':'application/json'}
else:
return render(request,"404.html",{})
I'm using the following to test it:
import requests
import json
# Update project_id
project_id = "<some_id>"
data = {"timestamp": 1463602412, "project_id": project_id, "data": {"cdn_url": "https://cdn.example.com/json/{0}.json".format(project_id), "origin_url": "https://example.s3.amazonaws.com/json/{0}.json".format(project_id), "revision": 15}, "event": "project.datafile_updated"}
r = requests.post("http://127.0.0.1:8000/ohook/", data=json.dumps(data), headers={'Content-Type': 'application/json'})
print r
It all works perfectly, but the tuple returned by webhook_event is giving me the following error:
Internal Server Error: /ohook/
Traceback (most recent call last):
File "/home/hassan/.virtualenvs/myenv/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 187, in get_response
response = middleware_method(request, response)
File "/home/hassan/.virtualenvs/myenv/local/lib/python2.7/site-packages/newrelic-2.56.0.42/newrelic/hooks/framework_django.py", line 328, in wrapper
return wrapped(*args, **kwargs)
File "/home/hassan/.virtualenvs/myenv/local/lib/python2.7/site-packages/django/middleware/clickjacking.py", line 30, in process_response
if response.get('X-Frame-Options', None) is not None:
AttributeError: 'tuple' object has no attribute 'get'
Can anyone help me diagnose this?
As the error says, you're returning a tuple: the JSON, an integer (presumably the status code) and a dict (presumably the headers).
Even if you fixed this, you can't just return JSON from a view; you have to return an instance of HttpResponse or one of its subclasses.
Instead of doing return json.dumps(...) in your POST blocks, you should use JsonResponse. This accepts a Python datastructure and returns a response containing the serialized JSON; as a bonus, it also sets the content-type appropriately.
if event_type == 'project.datafile_updated':
...
return JsonResponse({'success':True})
return JsonResponse({'success':False}, status=400)
(Note that render is explicitly a shortcut which renders a template and returns it as an HttpResponse; this isn't the case with json.dumps().)

how to parse key value pair request from url using python requests in flask

I have spent about a week on this issue and although I have made considerable progress I am stuck at a key point.
I am writing a simple client-server program in Python that is supposed to accept key/value pairs from the command line, formulate them into an url, and request the url from the server. The problem appears to be either that the url is not properly formatted or that the server is not parsing it correctly. (That is, the key-value pairs appear to be properly making it from the command line to the function that contains the request.)
import sys
import requests
server_url = "http://0.0.0.0:5006"
def test(payload):
print('payload in function is ' + payload)
r = requests.get('http://0.0.0.0:5006/buy', params=payload)
print(r.url) #debug
print(r.encoding)
print(r.text)
print(r.headers)
if __name__ == '__main__':
payload = sys.argv[2]
print('payload from cli is ' + payload)
test(payload)
Server:
import subprocess
from flask import Flask
from flask import request
import request
# Configure the app and wallet
app = Flask(__name__)
#app.route('/buy', methods=['GET', 'POST'])
def test(key1, key2):
key1 = str(request.args.get('key1'))
key2 = str(request.args.get('key2'))
print('keys are' + key1 + key2)
fortune = subprocess.check_output(['echo', 'key1'])
return fortune
# Initialize and run the server
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5006)
Client console output:
payload from cli is {"key1": "foo", "key2": "bar"}
payload in function is {"key1": "foo", "key2": "bar"}
http://0.0.0.0:5006/buy?%7B%22key1%22:%20%22foo%22,%20%22key2%22:%20%22bar%22%7D
ISO-8859-1
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
{'Content-Type': 'text/html', 'Content-Length': '291', 'Date': 'Fri, 09 Sep 2016 22:16:44 GMT', 'Server': 'Werkzeug/0.11.10 Python/3.5.2'}
Server console output:
* Running on http://0.0.0.0:5006/ (Press CTRL+C to quit)
[2016-09-09 18:30:33,445] ERROR in app: Exception on /buy [GET]
Traceback (most recent call last):
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1988, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1641, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1544, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.5/dist-packages/flask/_compat.py", line 33, in reraise
raise value
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1639, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1625, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
TypeError: test() missing 2 required positional arguments: 'key1' and 'key2'
127.0.0.1 - - [09/Sep/2016 18:30:33] "GET /buy?%7B%22key1%22:%20%22foo%22,%20%22key2%22:%20%22bar%22%7D HTTP/1.1" 500 -
I take this as showing that the subprocess call is for some reason not able to decipher key1 and key2 from the URL so is failing when it runs "fortune None None".
The problem seems to be in your payload.
The payload needs to be a dictionary. You're giving it a string.
sys.argv[2] will be a string, even if you format the text to look like a dictionary. So unless there's something missing from your client code snippet, payload isn't actually a dictionary like requests would expect.
I can infact confirm this by looking at the URL being generated, which is:
http://0.0.0.0:5006/buy?%7B%22key1%22:%20%22foo%22,%20%22key2%22:%20%22bar%22%7D
Had the payload been a dictionary and correctly encoded, it would've looked something like this:
http://0.0.0.0:5006/buy?key1=foo&key2=bar
To see what type payload your really is, do print(type(payload)) somewhere inside the test function.
Once you've converted your payload into a proper python dictionary (you'll have to parse your sys.argv[2]), then requests should work as expected.
test is expecting values for key1 and key2. Flask would provide those through your route.
#app.route('/buy/<key1>/<key2>')
def test(key1, key2):
Visiting /buy/value1/value2 would give values to the arguments. You want to pass values through the query string though.
You just need to remove them from the function's signature.
#app.route('/buy', methods=['GET', 'POST'])
def test():

Categories