KeyError on POST method in simple Flask python backend - python

I'm trying to create a simple API and server with MongoDB and Flask in python(pycharm). I'm testing the methods with Postman and so far the GET and DELETE methods work. I'm having troubles with the POST method mainly(for adding an entity). I have 2 classes
repository class
class ExercisesRepository:
def __init__(self):
self.client = MongoClient('localhost', 27017)
self.client.drop_database("exercise_database") # clear everything that was before
self.db = self.client.exercise_database # create database
self.exercises = self.db.exercises # create table in the database
def get_all(self):
return [{
'id': str(exercise['_id']),
'type': exercise['type'],
'calories': exercise['calories']
} for exercise in self.exercises.find()]
def add(self, exercise):
exercise = {key: exercise[key] for key in exercise}
exercise['calories'] = int(exercise['calories']) #line 24
self.exercises.insert_one(exercise) # automatically generates an ObjectId for the exercise
return 200
def update(self, exercise_id, exercise):
my_query = {"_id": ObjectId(exercise_id)}
new_values = {"$set": {"type": exercise["type"], "calories": exercise["calories"]}}
self.exercises.update_one(my_query, new_values)
return 200
def delete(self, exercise_id):
self.exercises.remove(ObjectId(exercise_id))
return 200
def check_database_content(self):
for exercise in self.exercises.find():
pprint.pprint(exercise)
server class
from ExercisesRepository import ExercisesRepository
from flask import Flask
from flask import request
from flask import jsonify
import sys
app = Flask(__name__)
exerciseRepo = ExercisesRepository()
exerciseRepo.add({'type': 'Yoga', 'calories': 500})
exerciseRepo.add({'type': 'Walk', 'calories': 300})
exerciseRepo.add({'type': 'Run', 'calories': 100})
#app.route('/')
def hello_world():
return 'Hello World!'
#app.route("/exercises", methods=['GET', 'POST'])
def exercises():
if request.method == 'GET':
return jsonify(exerciseRepo.get_all())
elif request.method == 'POST':
print(request.form, file=sys.stderr)
return jsonify(exerciseRepo.add(request.form)) #line 31
#app.route('/exercises/<exercise_id>', methods=['PUT', 'DELETE'])
def exercises_id(exercise_id):
if request.method == 'PUT':
print(request.form, file=sys.stderr)
return jsonify(exerciseRepo.update(exercise_id, request.form))
elif request.method == 'DELETE':
return jsonify(exerciseRepo.delete(exercise_id))
if __name__ == '__main__':
app.run()
When I try to make a POST call in postman with a JSON like this :
{
"type": "Aerobic",
"calories": 500
} I get the following message in postman: 500 Internal Server Error
Internal Server Error
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.
and in Pycharm console:
File "server.py", line 31, in exercises
return jsonify(exerciseRepo.add(request.form))
server\ExercisesRepository.py", line 24, in add
exercise['calories'] = int(exercise['calories'])
KeyError: 'calories'
127.0.0.1 - - [05/Jan/2020 13:01:50] "POST /exercises HTTP/1.1" 500 -
I'm pretty new to python and this is my first try to make an api so if you could explain as much as possible it would be very helpful. Thanks!

If you send data as JSON then you have to get it using request.json, not request.form
from flask import Flask, request, jsonify
app = Flask(__name__)
#app.route("/exercises", methods=['GET', 'POST'])
def exercises():
print('form:', request.form)
print('json:', request.json)
return jsonify(request.json) #line 31
if __name__ == '__main__':
app.run()
When you send as JSON
import requests
r = requests.post('http://localhost:5000/exercises', json={'type': 'Run', 'calories': 100})
then server shows
form: ImmutableMultiDict([])
json: {'type': 'Run', 'calories': 100}
When you send as form data
import requests
r = requests.post('http://localhost:5000/exercises', data={'type': 'Run', 'calories': 100})
then server shows
form: ImmutableMultiDict([('type', 'Run'), ('calories', '100')])
json: None
form sends data as string type=Run&calories=100 in body,
json sends data as string {'type': 'Run', 'calories': 100} in body.
You can see it if you display request's body
import requests
r = requests.post('https://httpbin.org/post', data={'type': 'Run', 'calories': 100})
print(r.request.body)
r = requests.post('https://httpbin.org/post', json={'type': 'Run', 'calories': 100})
print(r.request.body)
Result
type=Run&calories=100
b'{"type": "Run", "calories": 100}'

Related

Flask preventing PUT, DELETE, and POST

I am trying to test a Flask script I have written, by adding, amending, and deleting information. However, if I run anything other than methods=["GET"], I get an error saying that the method is not allowed.
The script I am running is:
#!flask/bin/python
from flask import Flask, jsonify, request, abort, make_response
from flask_cors import CORS
import csv
stocks = [
{"id": 1, "Stock": "BoI", "Price": 300},
{"id": 2, "Stock": "Apple", "Price": 499}
]
Next_ID = len(stocks)
app = Flask(__name__, static_url_path="", static_folder=".")
CORS(app, support_credentials=True)
#app.route("/")
#app.route("/Stocks")
def getAll():
return jsonify(stocks)
# curl http://127.0.0.1:5000
#app.route("/Stocks/<int:id>")
def getById(id):
sel_stock = list(filter(lambda s: s["id"] == id, stocks))
if len(sel_stock) == 0:
return jsonify ({}), 204
else:
return jsonify(sel_stock[0])
# curl http://127.0.0.1:5000/1
#app.route("/Stocks", methods=["POST"])
def create(id):
global Next_ID
if not request.json:
abort(400)
stock = {
"id": Next_ID,
"Stock": request.json["Stock"],
"Price": request.json["Price"],
}
Next_ID += 1
stocks.append(stock)
return jsonify(stock)
# return str(Next_ID)
#app.route("/Stocks", methods=["PUT"])
def update(id):
sel_stock = list(filter(lambda s: s["id"] == id, stocks))
if len(sel_stock) == 0:
abort(400)
sel_stock = sel_stock[0]
if not request.json:
abort(400)
reqJSON = request.json
if reqJSON["Stock"]:
sel_stock["Stock"] = reqJSON["Stock"]
if reqJSON["Price"]:
sel_stock["Price"] = reqJSON["Price"]
return jsonify(sel_stock)
#app.route("/Stocks", methods=["DELETE"])
def delete(id):
return "in delete"
if __name__ == "__main__":
app.run(debug = True)
If I run curl commands to get all the information or get the information by ID number, there is no issue. However, If I try the following curl command I get an error:
curl -i -H "Content-Type:application/json" -X POST -d "{\"Stock\":\"IBM\",\"Price\":\123}" http://127.0.0.1:5000/Stocks
TypeError: create() missing 1 required positional argument: 'id'
You do not need to pass id to your POST method since its url does not have id as parameter:
#app.route("/Stocks", methods=["POST"])
def create(): # removed id
"""some code here"""

Can't implement braintree subscription on Flask

I have this app
import braintree
from flask import Flask, render_template, send_from_directory, request
try:
from ConfigParser import SafeConfigParser
except ImportError:
from configparser import SafeConfigParser
app = Flask(__name__)
parser = SafeConfigParser()
parser.read('secrets.ini')
MERCHANTID = parser.get('braintree', 'MERCHANTID')
PUBLICKEY = parser.get('braintree', 'PUBLICKEY')
PRIVATEKEY = parser.get('braintree', 'PRIVATEKEY')
braintree.Configuration.configure(braintree.Environment.Sandbox,
merchant_id=MERCHANTID,
public_key=PUBLICKEY,
private_key=PRIVATEKEY)
#app.route("/")
def index():
# Generate client token for the dropin ui
client_token = braintree.ClientToken.generate({})
return render_template('index.html', token=client_token)
#app.route("/proc", methods=['GET', 'POST'])
def proc():
# result = braintree.Transaction.sale({
# "amount": request.form["amount"],
# "payment_method_nonce": request.form["payment_method_nonce"]
# })
# print(result)
result = braintree.Customer.create({
"credit_card": {
"number": "4111111111111111",
"expiration_date": "12/16"
}
})
result = braintree.Subscription.create({
"payment_method_token": result.customer.credit_cards[0].token,
"plan_id": "upwork_sub"
})
return render_template('proc.html', result=result, request=request.form)
if __name__ == "__main__":
app.run(host='0.0.0.0')
The one time charge(the code that is commented) works fine.
The problem is when I want to charge with subscription I have to hard create the Client and then auto charge with subscription.
How I can make it without hard coding the clients info

Flask AssertionError: 400 != 201 [duplicate]

This question already has answers here:
How to send requests with JSON in unit tests
(2 answers)
Closed 4 years ago.
I am trying to write a test case for my post end point, but I seriously do not know what I am doing wrong.
views.py
The user can post a product by typing the name, the price and category I have hard coded:
from flask import Flask, request
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
products = []
class Product(Resource):
def post(self, name):
if next(filter(lambda x: x['name'] == name, products), None):
return {'message':
"A product with name '{}' already exists."
.format(name)}, 400
data = request.get_json()
product = {'id': len(products) + 1,
'name': name, 'price': data['price'],
'category': data['category']}
products.append(product)
return product, 201
When I run it I get the JSON data with a 201 OK response.
{
"price": 50,
"category": "stationery",
"name": "pencil",
"id": 2
}
But when I test it:
test_product.py
import unittest
import json
from app import create_app
class ProductTestCase(unittest.TestCase):
"""This is the class for product test cases"""
def setUp(self):
self.app = create_app('testing')
self.client = self.app.test_client
def test_to_add_product(self):
"""Test method to add product"""
"""Is this how the json payload is passed"""
add_product = self.client().post('/api/v1/product/pencil',
data={
"price": 50,
"name": "pencil",
"category": "stationery"
},
content_type='application/json')
self.assertEqual(add_product.status_code, 201)
I get the following error:
E AssertionError: 400 != 201
app/tests/v1/test_product.py:22: AssertionError
What does this mean, and how can I fix it?
EDIT
folowing this answer,How to send requests with JSONs in unit tests, this worked for me:
def test_to_add_product(self):
"""Test method to add product"""
add_product = self.client().post('/api/v1/product/pencil',
data=json.dumps(
dict(category='category',
price='price')),
content_type='application/json')
self.assertEqual(add_product.status_code, 201)
HTTP 400 means "bad request". It indicates that the request you're sending to your API endpoint is somehow invalid.
I don't see where you're passing any JSON data to your endpoint in your test. If JSON data is required (it appears to be since you have data = request.get_json() in your code) that would explain the bad request response.
If you include a valid JSON payload in your test you should see the response you expect.

Python keeps KeyError: 'message' when fetch data from Messenger Callback - Python/flask

I've tried to build a simple bot on Messenger that echoing back whenever we send a message to it.
The message is sent successfully so far but in the log, it keeps displaying error:
message_id = data['entry'][0]['messaging'][0]['message']['mid']
KeyError: 'message'
I have no idea why it happen, this is the logic operations:
import requests
import traceback
from flask import request
from ...config.default import VERIFY_TOKEN, ACCESS_TOKEN
ROOT_URL = "https://graph.facebook.com/v2.6/me/messages?access_token="
def reply(user_id, page_id, msg):
data = {
"sender": {"id": page_id},
"recipient": {"id": user_id},
"message": {"text": msg}
}
requests.post(ROOT_URL + ACCESS_TOKEN, json=data)
def verification_handler():
if request.args['hub.verify_token'] == VERIFY_TOKEN:
return request.args['hub.challenge'], 200
return "Invalid verification token"
def handle_incoming_messages(data):
try:
# Get all data from Messenger callback - Text
recipient_id = data['entry'][0]['messaging'][0]['recipient']['id']
sender_id = data['entry'][0]['messaging'][0]['sender']['id']
timestamp = data['entry'][0]['messaging'][0]['timestamp']
message_id = data['entry'][0]['messaging'][0]['message']['mid']
text = data['entry'][0]['messaging'][0]['message']['text']
reply(sender_id, recipient_id, text)
except KeyError:
print(traceback.format_exc())
return ''
this is the routes:
from flask import Blueprint, request
from .ops import verification_handler, handle_incoming_messages
mod = Blueprint('messenger', __name__)
# Route: /messenger/
#mod.route('/', methods=['GET'])
def verify():
"""Facebook will GET request to this endpoint for verification."""
return verification_handler()
# Route: /messenger/
#mod.route('/', methods=['POST'])
def handle():
data = request.json
handle_incoming_messages(data)
return ''
# Route: /messenger/hello
#mod.route('/hello')
def hello():
return 'hello there'
I don't have enough reputation to leave a comment so I have to post this here.
From the Python Wiki
Python raises a KeyError whenever a dict() object is requested (using the format a = adict[key]) and the key is not in the dictionary.
Are you sure the key 'message' exists?
I do this to extract the info
if keys_exist(event, ['body']):
event_entry=json.loads(event['body'])
if ((len(event_entry['entry'])>0) & (keys_exist(event_entry['entry'][0],['messaging'])) ):
messaging_event = event_entry['entry'][0]['messaging'][0]
if (keys_exist(messaging_event,['message'])):
msg_txt = messaging_event['message']['text']
sender_id = messaging_event['sender']['id']

How to handle multiple WSGI applications in Flask?

I'm a newbie in Flask and was wondering how to implement Flask API (http://www.flaskapi.org/) along with the main website, for example, main website (/), access API (/api/). In fact, this gives me an Internal Server Error.
Thank you.
from flask import Flask, request, url_for
from flask.ext.api import FlaskAPI, status, exceptions
# the all-important app variable:
app = Flask(__name__)
app2 = FlaskAPI(__name__)
#app.route("/")
def hello():
return "Oh, Hello Worldaa"
notes = {
0: 'do the shopping',
1: 'build the codez',
2: 'paint the door',
}
def note_repr(key):
return {
'url': request.host_url.rstrip('/api/') + url_for('notes_detail', key=key),
'text': notes[key]
}
#app2.route("/api/", methods=['GET', 'POST'])
def notes_list():
"""
List or create notes.
"""
if request.method == 'POST':
note = str(request.data.get('text', ''))
idx = max(notes.keys()) + 1
notes[idx] = note
return note_repr(idx), status.HTTP_201_CREATED
# request.method == 'GET'
return [note_repr(idx) for idx in sorted(notes.keys())]
#app2.route("/api/<int:key>/", methods=['GET', 'PUT', 'DELETE'])
def notes_detail(key):
"""
Retrieve, update or delete note instances.
"""
if request.method == 'PUT':
note = str(request.data.get('text', ''))
notes[key] = note
return note_repr(key)
elif request.method == 'DELETE':
notes.pop(key, None)
return '', status.HTTP_204_NO_CONTENT
# request.method == 'GET'
if key not in notes:
raise exceptions.NotFound()
return note_repr(key)
if __name__ == "__main__":
app.run(debug=True)
app2.run(debug=True)
You can look at Blueprint in the documentation of Flask.
For example:
main = Blueprint(__name__)
#main.route("/")
def index():
return "Hello, World."
api = Blurprint(__name__)
#api.route("/test")
def test():
return jsonify(dict(test="test")})
app = Flask(__name__)
app.register_blueprint(main)
app.register_blueprint(api, url_prefix="/api")
and then, you access index page by http://example.com, access api by http://example.com/api/test

Categories

Resources