How to perform POST action on resources using Flask - python

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.

Related

Migrating a Python Backend from Flask to Tornado

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!'})

Spotify API create playlist - error parsing JSON

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')

How to display an error message on invalid url?

#app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
session['link'] = request.form.get('link')
link = YouTube(session['link'])
return render_template('video.html', link=link)
return render_template('index.html')
#app.route('/download', methods=['GET', 'POST'])
def download():
if request.method == "POST":
link = YouTube(session['link'])
itag = request.form.get('itag')
video = link.streams.get_by_itag(itag)
filename = video.download()
return send_file(filename, as_attachment=True)
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)
I want my youtube downloader code to display an error message when the user enters an invalid URL or if the video is unavailable. It is showing an internal server error if the user enters an invalid URL.
After changes:
#app.route('/', methods=['GET', 'POST'])
def index():
try:
if request.method == 'POST':
session['link'] = request.form.get('link')
link = YouTube(session['link'])
return render_template('video.html', link=link)
return render_template('index.html')
except:
return redirect(url_for('index'))
Now if the user enters the invalid URL the user gets redirected to the homepage but I want to Flash error message once the user gets redirected to the homepage.
Instead of you current API call, you can use the catch method with the new Fetch method in Javascript:
fetch('https://example.com/profile', {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
The catch method triggers anytime when an error occurs. What you can do is specify different statements to output and check different crash criteria.
Sources:
Using Fetch

Logging into an external website with flask

I'm currently having some trouble with my flask webapp, where I have written it as below, but when I try to run the flask app, I run into a Bad Request Error. (The browser (or proxy) sent a request that this server could not understand)
Essentially, I am trying to allow users to log in to an external website through the flask webapp
What is the cause of this error? Apologies if I am making a stupid mistake, I'm very new to flask.
from flask import Flask,render_template, request, redirect
import requests
from bs4 import BeautifulSoup as bs
app = Flask(__name__)
#app.route('/', methods = ["POST", "GET"])
def login():
username = request.form['username']
pin = request.form['password']
s = requests.Session()
r = s.get("https://www.example.com/User/Login")
soup = bs(r.text, 'html.parser')
loginToken = soup.findAll(attrs={"name" : "__RequestVerificationToken"})[0]['value']
#Create Login Payload
login_payload = {
"__RequestVerificationToken" : loginToken,
"UserName" : username,
"Password" : pin,
"returnUrl" : "https://example.com/user-action/?action=login&returnUrl=https://www.example.com/User/Information",
}
#Post Login Payload
r = s.post("https://www.example.com/Account/Login", data = login_payload)
if r.status_code == 200:
return render_template('home.html')
else:
return render_template('login.html')
return render_template('login.html')
#app.route('/home') #If login works, redirect to this page
def hello_world():
return 'Hello, World!'
if __name__ == "__main__":
app.run(debug = True)
In addition, if there are other resources that I could refer to with regards to allowing a user to log in to a external URL from the flask webapp as compared to the conventional tutorials that only show a user logging in to the flask webapp itself, do share it with me, thank you!
Your endpoint' s has two Http verbs ["POST", "GET"]. You should specify your methods as below.
#app.route('/', methods = ["POST", "GET"])
def login():
if request.method == "GET":
#something do stuff
return render_template("your_html_page")
if request.method == "POST":
#something do stuff
return your_response, 200
Edited Block
#app.route('/', methods = ["POST", "GET"])
def login():
if request.method == "GET":
return render_template('login.html')
if request.method == "POST":
#same logic here
if status_code == 200:
return redirect(url_for('home'))
return render_template('login.html')

Flask Redirect URL for page not found (404 error)

I want to know which way is better to handle page not found error 404. So I have found two ways to redirect pages when someone tries to go to my website, bu they type in a url that I do not have a route built for. The first way is to build an error handler so I could do one like this:
#app.errorhandler(404)
def internal_error(error):
return redirect(url_for('index'))
There is a second method that I found via the flask website, http://flask.pocoo.org/snippets/57/, and is like this:
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def catch_all(path):
return redirect(url_for('index'))
The difference is that one would be handling the error and the other is a dynamic routing. But what would be better to use? I don't really know what the pros of cons would be and before deploying one I would like to better understand it.
To help this is my base code:
#app.route('/', methods=["GET", "POST"])
def index():
return render_template("HomePage.html")
if __name__ == "__main__":
app.debug = True
app.run()
I'll say to handle your scenario, the second approach is good as you need the handle the route that not exist
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def catch_all(path):
return redirect(url_for('index'))
But in general, both have different use-cases, let say, you are rendering multiple templates in different functions and you got an exception while rendering it. So, to cater this situation you need to do implement exception handling in all the method.
Henceforth, instead of doing this and to make our readable and scalable, we can register the exception with this function and create a custom response for the user, refer below:
# It will catch the exception when the Template is not found and
# raise a custom response as per the rasied exception
#app.errorhandler(TemplateNotFound)
def handle_error(error):
message = [str(x) for x in error.args]
status_code = 500
success = False
response = {
'success': success,
'error': {
'type': error.__class__.__name__,
'message': message
}
}
return jsonify(response), status_code
# For any other exception, it will send the reponse with a custom message
#app.errorhandler(Exception)
def handle_unexpected_error(error):
status_code = 500
success = False
response = {
'success': success,
'error': {
'type': 'UnexpectedException',
'message': 'An unexpected error has occurred.'
}
}
return jsonify(response), status_code

Categories

Resources