I've already created a working CRUD app with the backend done in Python-Flask and Python-PyMongo, but now I need to migrate the backend from Flask to Tornado. There isn't very much up to date documentation online for Tornado, and bear in mind also that I just started learning web dev two weeks ago. My Flask backend looks like this:
from flask import Flask, request, jsonify
from flask_pymongo import PyMongo, ObjectId
from flask_cors import CORS
app = Flask(__name__)
app.config["MONGO_URI"]="mongodb+srv://<user>:<pass>#cluster0.f0zvq.mongodb.net/myFirstDatabase?retryWrites=true&w=majority"
mongo = PyMongo(app)
CORS(app)
db = mongo.db.users
#GET and POST responses
#app.route('/users', methods=['GET', 'POST'])
def createUser():
if request.method == "GET":
users = []
for doc in db.find():
users.append({
'_id': str(ObjectId(doc['_id'])),
'username': doc['username'],
'firstName': doc['firstName'],
'lastName': doc['lastName'],
'dob': doc['dob']
})
return jsonify(users)
elif request.method == "POST":
id = db.insert_one({
'username': request.json['username'],
'firstName': request.json['firstName'],
'lastName': request.json['lastName'],
'dob': request.json['dob']
})
return jsonify({'id': str(id.inserted_id), 'msg': 'User Added Successfully!'})
{...}
if __name__ == "__main__":
app.run(debug=True)
So my attempt at migrating it to Tornado (based on what I could find online) is something like this:
import tornado.ioloop
import tornado.web
import urllib.parse
from bson.json_util import dumps
from bson import ObjectId
from pymongo import MongoClient
cluster = MongoClient("mongodb+srv://<user>:<pass>#cluster0.vsuex.mongodb.net/myFirstDatabase?retryWrites=true&w=majority")
db = cluster["test"]
collection = db["test"]
class UserHandler(tornado.web.RequestHandler):
#This one is working
def get(self):
users = []
for doc in collection.find():
users.append({
'_id': str(doc['_id']),
'username': doc['username'],
'firstName': doc['firstName'],
'lastName': doc['lastName'],
'dob': doc['dob']
})
self.write(json.dumps(users))
def post(self):
body = urllib.parse.urlparse(self.request.body)
for key in body:
#having trouble at this line
body[key] = body[key][0]
id = collection.insert_one({
"username": body["username"],
"firstName": body["firstName"],
"lastName": body["lastName"],
"dob": body["dob"]
})
self.write(dumps({'id': str(id.inserted_id), 'msg': 'User Added Successfully!'}))
{...}
def make_app():
return tornado.web.Application([
(r"/users", UserHandler)
],
debug = True,
autoreload = True)
if __name__ == "__main__":
app = make_app()
port = 8888
app.listen(port)
print(f"🌐 Server is listening on port {8888}")
#start server on current thread
tornado.ioloop.IOLoop.current().start()
The error I'm getting so far from Postman when I attempt to post some data is:
line 56, in post
body[key] = body[key][0]
TypeError: tuple indices must be integers or slices, not bytes
Any help is appreciated, thanks!
Solved the function for POST requests:
async def post(self):
user = tornado.escape.json_decode(self.request.body)
id = await collection.insert_one({
"username": user["username"],
"firstName": user["firstName"],
"lastName": user["lastName"],
"dob": user["dob"]
})
self.set_header('Content-Type', 'application/json')
return self.write({'id': str(id.inserted_id), 'msg': 'User Added
Successfully!'})
Related
I have a Flask app where I want to create playlists using Spotify API. My issue is similar to this Stackoverflow question.
The difference is that I am using OAuthlib instead of requests and the solution posted there didn't work in my case.
The problem
In the http request, when I set data={'name': 'playlist_name', 'description': 'something'},
I am getting a response: "error": {"status": 400,"message": "Error parsing JSON."}
But when I follow the answer mentioned above and try this: data=json.dumps({'name': 'playlist_name', 'description': 'something'}),
I am getting following error in the console: "ValueError: not enough values to unpack (expected 2, got 1)".
How I can fix this? Here is a simplified version of my app:
app.py
from flask import Flask, url_for, session
from flask_oauthlib.client import OAuth
import json
app = Flask(__name__)
app.secret_key = 'development'
oauth = OAuth(app)
spotify = oauth.remote_app(
'spotify',
consumer_key=CLIENT,
consumer_secret=SECRET,
request_token_params={'scope': 'playlist-modify-public playlist-modify-private'},
base_url='https://accounts.spotify.com',
request_token_url=None,
access_token_url='/api/token',
authorize_url='https://accounts.spotify.com/authorize'
)
#app.route('/', methods=['GET', 'POST'])
def index():
callback = url_for(
'create_playlist',
_external=True
)
return spotify.authorize(callback=callback)
#app.route('/playlist', methods=['GET', 'POST'])
def create_playlist():
resp = spotify.authorized_response()
session['oauth_token'] = (resp['access_token'], '')
username = USER
return spotify.post('https://api.spotify.com/v1/users/' + username + '/playlists',
data=json.dumps({'name': 'playlist_name', 'description': 'something'}))
#spotify.tokengetter
def get_spotify_oauth_token():
return session.get('oauth_token')
if __name__ == '__main__':
app.run()
You are using the data parameter, which takes a dict object, but you are dumping it to a string, which is not necessary. Also, you have to set the format to json, as follow:
#app.route('/playlist', methods=['GET', 'POST'])
def create_playlist():
resp = spotify.authorized_response()
session['oauth_token'] = (resp['access_token'], '')
username = USER
return spotify.post('https://api.spotify.com/v1/users/' + username + '/playlists',
data={'name': 'playlist_name', 'description': 'something'}, format='json')
I'm building a webapp with React and Flask and I have an issue with POST request.
This is my app.py file:
import sys
import os
from flask import Flask, jsonify, request, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS, cross_origin
from flask_mail import Mail, Message
from models import User, Project, Image, db
from api import blueprints
from tools import format_email
app = Flask(__name__,
static_folder='../build/static',
template_folder="../build"
)
app.config.from_object(os.environ['APP_SETTINGS'])
db.init_app(app)
cors = CORS(app)
mail = Mail(app)
# Register the blueprints
for b in blueprints:
app.register_blueprint(b)
#cross_origin
#app.route('/', defaults={'u_path': ''})
#app.route('/<path:u_path>')
def index(u_path=None):
return render_template("index.html")
#app.route('/api/process_email', methods=['POST'])
def process_email():
print('plop')
data = request.get_json()
formated_email = format_email(
data['firstname'],
data['lastname'],
data['email'],
data['message']
)
msg = Message(formated_email['title'], sender='plop#gmail.com', recipients=['plop#gmail.com'])
msg.body = formated_email['textbody']
msg.html = formated_email['htmlbody']
mail.send(msg)
return 'done'
if __name__ == "__main__":
app.run()
In the config.py I have this set CORS_HEADERS = 'Content-Type'
When I test this with Postman, my email is sent without any issue. But from the app, I get a 405 METHOD NOT ALLOWED response.
This is how I send the request:
axios.post(API_URL + 'process_email/', {
"firstname": values.firstname,
"lastname": values.lastname,
"email": values.email,
"message": values.message
}, {
headers: {
'Content-Type': 'text/plain;charset=utf-8',
},
withCredentials: 'same-origin'
})
.then(({ data }) => {
console.log(data)
}, (error) => {
toast.error("gneuuuu");
})
.catch(() => {toast.error("Oups! Something went wrong!")});
This is what I have:
Since I put a proxy in place, I don't see preflights request anymore.
I tried with a simple fetch or use superagent but issue is still here, and I clearly don't understand how CORS works. Any help would be appreciated. Thanks!
If anyone needs the answer: issue was in the way I was sending the request with axios. Instead of:
axios.post(API_URL + 'process_email/', {...
I have to remove the trailing / ! This works fine:
axios.post(API_URL + 'process_email', {...
i am learning python and the REST API with Flask. In one of the tutorials the below mentioned method is used to do POST
operation to the resources.
#app.route('/todo/api/v1.0/createTask', methods=['POST'])
def create_task():
if not request.json or not 'title' in request.json:
abort(400)
task = {
'id': tasks[-1]['id'] + 1,
'title': request.json['title'],
'description': request.json.get('description', ""),
'done': False
}
tasks.append(task)
return jsonify({'task': task}), 201
at runtime when i access the following link
http://127.0.0.1:5000/todo/api/v1.0/createTask
i receive the following error:
Method Not Allowed
The method is not allowed for the requested URL.
please let me know how to fix this error
code:
from flask import Flask, jsonify
from flask import abort
from flask import make_response
from flask import request
app = Flask(__name__)
tasks = [
{
'id': 1,
'title': u'Buy groceries',
'description': u'Milk, Cheese, Pizza, Fruit, Tylenol',
'done': False
},
{
'id': 2,
'title': u'Learn Python',
'description': u'Need to find a good Python tutorial on the web',
'done': False
}
]
#app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)
#app.route('/todo/api/v1.0/tasks', methods=['GET'])
def get_tasks():
return jsonify({'tasks': tasks})
#app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['GET'])
def get_task(task_id):
'''
more appropriate for error handlering
'''
task = [task for task in tasks if task['id'] == task_id]
if len(task) == 0:
return not_found("")
return jsonify({'task': task[0]})
#app.route('/todo/api/v1.0/createTask', methods=['POST'])
def create_task():
if not request.json or not 'title' in request.json:
abort(400)
task = {
'id': tasks[-1]['id'] + 1,
'title': request.json['title'],
'description': request.json.get('description', ""),
'done': False
}
tasks.append(task)
return jsonify({'task': task}), 201
if __name__ == '__main__':
app.run(debug=True)
update:
i would like to know the purpose of
if not request.json or not 'title' in request.json:
abort(400)
what does
if not request.json or not 'title' in request.json:
mean?
When you send out the request, did you do it in the browser? I think it will send as a GET request by default for http://127.0.0.1:5000/todo/api/v1.0/createTask.
If you really want to send a POST request, you can do it via your terminal (curl) or maybe try to use postman.
curl -X POST http://127.0.0.1:5000/todo/api/v1.0/createTask
curl -X POST -d '{"title": "AAA", "description": "AAADESCRIPTION"}' -H 'Content-Type: application/json' http://127.0.0.1:5000/todo/api/v1.0/createTask
Or if you think the GET method should also be accepted for this endpoint, then you can update your code (Not really recommended, because it is against the initial definition difference between POST and GET).
# #app.route('/todo/api/v1.0/createTask', methods=['POST', 'GET'])
#app.route('/todo/api/v1.0/createTask', methods=['POST'])
def create_task():
request_params = request.get_json()
if not request_params or 'title' not in request_params:
abort(400)
task = {
'id': tasks[-1]['id'] + 1,
'title': request_params['title'],
'description': request_params.get('description', ""),
'done': False
}
tasks.append(task)
return jsonify({'task': task}), 201
you need to count also for the [GET] method, because what your actually trying to do is get not POST
add
`
methods = ["GET", "POST"]
if request.method == "post":
your code here
else:
return render_template("createtasks")
`you need to specify the get method for the createTask, i dont know how you did it because its not mentioned in your code but i assumed its a template.
I've setup a database and an app using flask and sqlalchemy, the get request is working fine, but the post request doesn't work at all, please support me here if you can, below you will have the code which is just a small part of my project, the get request is working fine but the post no.
Data base setup
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy import create_engine
from passlib.apps import custom_app_context as pwd_context
import random, string
from itsdangerous import(TimedJSONWebSignatureSerializer as Serializer, BadSignature, SignatureExpired)
Base = declarative_base()
class Relation(Base):
__tablename__ = 'relation'
id = Column(Integer, primary_key=True)
base_user_first = Column(String(250), nullable=False)
base_user_second = Column(String(250), nullable=False)
relation = Column(String(250), nullable=False)
reverse_relation = Column(String(250), nullable=False)
#property
def serialize(self):
return {
'base_user_first': self.base_user_first,
'id': self.id,
'base_user_second': self.base_user_second,
'relation': self.relation,
'reverse_relation': self.reverse_relation,
}
# creating the DB.
engine = create_engine('sqlite:///myneighbour.db')
Base.metadata.create_all(engine)
app code
from database_setup import Base, Relation
from flask import Flask, jsonify, request, url_for, abort, g
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy import create_engine
from flask_httpauth import HTTPBasicAuth
from flask import session as login_session
import random
import string
from oauth2client.client import flow_from_clientsecrets
from oauth2client.client import FlowExchangeError
import httplib2
import json
from flask import make_response
import requests
auth = HTTPBasicAuth()
engine = create_engine('sqlite:///myneighbour.db')
Base.metadata.bind = engine
DBSession = sessionmaker(bind=engine)
session = DBSession()
app = Flask(__name__)
#app.route('/relation/<int:relation_id>/JSON')
def relationJSON(relation_id):
relation = session.query(Relation).filter_by(id=relation_id).one()
items = session.query(Relation).filter_by(
relation_id=relation_id).all()
return jsonify(Relation=[i.serialize for i in items])
# view all the relations
#app.route('/relation/JSON')
def relationsJSON():
relations = session.query(Relation).all()
return jsonify(relations=[r.serialize for r in relations])
# Create a new Relation
#app.route('/relation/new/', methods=['GET', 'POST'])
def newRelation():
# if 'username' not in login_session:
# return 405
if request.method == 'POST':
newRelation = Relation(
base_user_first=request.form['base_user_first'],
base_user_second=request.form['base_user_second'],
relation=request.form['relation'],
reverse_relation=request.form['reverse_relation'])
session.add(newRelation)
#flash('New Facility %s Successfully Created' % newFacility.name)
session.commit()
return jsonify (newRelation)
# Edit a Relation
#app.route('/relation/<int:relation_id>/edit/', methods=['GET', 'POST'])
def editRelation(relation_id):
editedRelation = session.query(
Relation).filter_by(id=relation_id).one()
# if 'username' not in login_session:
# return 405
# if editedFacility.user_id != login_session['user_id']:
# return "<script>function myFunction() {alert('You are not authorized \
# to edit this restaurant. Please create your own restaurant in order to\
# edit.');}</script><body onload='myFunction()'>"
if request.method == 'POST':
if request.form['base_user_first']:
editedRelation.base_user_first = request.form['base_user_first']
if request.form['base_user_second']:
editedRelation.base_user_second = request.form['base_user_second']
if request.form['relation']:
editedRelation.relation = request.form['relation']
if request.form['reverse_relation']:
editedRelation.reverse_relation = request.form['reverse_relation']
#flash('Restaurant Successfully Edited %s' % editedRestaurant.name)
return jsonify(editedRelation)
# else:
# return render_template(
# 'editRestaurant.html', restaurant=editedRestaurant
# )
# Delete a relation
#app.route('/relation/<int:relation_id>/delete/', methods=['GET', 'POST'])
def deleteRelation(relation_id):
relationToDelete = session.query(
Relation).filter_by(id=relation_id).one()
# if 'username' not in login_session:
# return 405
# if facilityToDelete.user_id != login_session['user_id']:
# return "<script>function myFunction() {alert('You are not authorized \
# to delete this restaurant. Please create your own restaurant in order \
# to delete.');}</script><body onload='myFunction()'>"
if request.method == 'POST':
session.delete(relationToDelete)
#flash('%s Successfully Deleted' % restaurantToDelete.name)
session.commit()
return ("the relation has been deleted")
if __name__ == '__main__':
app.debug = True
#app.config['SECRET_KEY'] = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(32))
app.run(host="0.0.0.0", port=5000, debug=True)
and here is what I receive when I make the post request through postman by using the /relation/new endpoint
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>400 Bad Request</title>
<h1>Bad Request</h1>
<p>The browser (or proxy) sent a request that this server could not understand.</p>
Replace all occurrences of request.form['base_user_first'] and the like to request.form.get('base_user_first')
Form sending error, Flask
You are receiving a Bad Request error from the server. This implies that your frontend(HTML form) is not sending the expected parameters to the server. For example, if your server is expecting field data from client via post like this:
#app.route('/user', methods=["GET", "POST"])
def userdata():
name = request.form['name']
email = request.form['email']
....
And your front end sends something like this:
fetch('/users', {
method: 'POST',
body: {
nam: "Typo in name",
email: "email#somethig.com"
},
})
The server expects name but the frontend code sent **nam** which will generate a Bad Request(400) error.
Bottom line is, make sure all request data from the frontend matches the ones expected by the server.
I'm currently working on creating a Cookie from an endpoint. As my backend and frontend only interacts via RESTful endpoints, is there anyway I can create a cookie when the frontend calls my backend's endpoint?
flask.make_response.set_cookie() doesn't seem to work for me. Also, I can't use app.route('/') to set my cookie either.
You can do this with Set-Cookie header returning with a response.
from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def get(self):
return {'task': 'Hello world'}, 200, {'Set-Cookie': 'name=Nicholas'}
api.add_resource(HelloWorld, '/')
if __name__ == '__main__':
app.run(debug=True)
Setting the header in the response tuple is one of the standard approaches. However, keep in mind that the Set-Cookie header can be specified multiple times, which means that a python Dictionary won't be the most effective way to set the cookies in the response.
According to the flask docs the header object can also be initialized with a list of tuples, which might be more convenient in some cases.
Example:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__, static_url_path='')
api = Api(app)
class CookieHeaders(Resource):
def get(self):
# Will only set one cookie "age = 23"
return { 'message' : 'Made with dict'}, 200, { 'Set-Cookie':'name=john', 'Set-Cookie':'age=23' }
def post(self):
# Will set both cookies "name = john" and "age = 23"
headers = [ ('Set-Cookie', 'name=john'), ('Set-Cookie', 'age=23') ]
return { 'message' : ' Made with a list of tuples'}, 200, headers
api.add_resource(CookieHeaders, '/')
if __name__ == '__main__':
app.run(debug=True)
The GET call will only set 1 cookie (due to the lack of multi-key support in python dictionaries), but the POST call will set both.
Flask has a #after_this_request callback decorator. (see: http://flask.pocoo.org/docs/1.0/api/#flask.after_this_request)
so you can set your cookies in it
from flask import after_this_request
from flask_restful import Resource
class FooResource(Resource):
def get(self):
#after_this_request
def set_is_bar_cookie(response):
response.set_cookie('is_bar', 'no', max_age=64800, httponly=True)
return response
return {'data': 'foooo'}
or even
from flask import after_this_request, request
from flask_restful import Resource, abort
class FooResource(Resource):
def get(self):
self._check_is_bar()
return {'data': 'foooo'}
def _check_is_bar(self)
if request.cookies.get('is_bar') == 'yes':
abort(403)
#after_this_request
def set_is_bar_cookie(response):
response.set_cookie('is_bar', 'no', max_age=64800, httponly=True)
return response