First time with Flask-Python -Module2

Building my first Flask API

Module 2 :

Python | Flask API – POSTING Data

Welcome back,

This is our second module of building our flask API. In our previous module we built our first flask application using Python and developed our route decorator for GET method.

In this module, we will discuss about the following:

2.POSTING DATA

2.1 Adding a POST route

2.2 Getting the Request body sent by the client

2.3 Sending POST request to our Flask API using POSTMAN app

2.4 Sanitizing Data sent in POST request

2.5 Testing how well we sanitized

2.6 Adding New book to our Store

2.7 Mistake to be aware

2.8 Setting Status codes and Response

2.9 Setting location Headers

2.10 Handling invalid POST requests

By the end of this module, you will be able to do POST request with a clean Sanitized Data which returns a meaningful Status Code and Response.

Lets get inside the module..

2.POSTING DATA

2.1 Adding a POST route

#adding POST request

@app.route(‘/books’, methods=[‘POST’]) #POST request will be fired

def add_book(): #allows client to add a new entry

pass #empty placeholder(we will define later)

Runnable Code Snippet_2.1- Adding a POST route:

# 2.1 Adding a POST route

from flask import Flask, jsonify ,request #request library

#app instance
app = Flask(__name__)
print(__name__)

#creating List with 2 dictionaries
books = [
	{
		'name' : 'Book1',
		'price' : 10,
		'isbn' : 987654
	},
	{
		'name' : 'Book2',
		'price' : 20,
		'isbn' : 123456
	}
]

#route decorators

#GET/books
@app.route('/books') 
def get_all_books():
	return jsonify({'Menu': books})

#adding POST request
@app.route('/books', methods=['POST']) #POST request will be fired
def add_book():	    #allows client to add a new entry
	pass        #empty placeholder(we will define later)


#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
	return_value={} #empty dictionary to hold ret_val
	for book in books:
		if book["isbn"] == isbn:
			return_value = {
				'name' : book["name"],
				'price' : book["price"]
			}
	return jsonify(return_value)		

app.run(port=5000)

Save and Run from CLI, to check the code is working without any error. We are yet to define the method.

2.2 Getting the Request body sent by the client

In POST request, details of the new entry must be given by the Client in their RESPONSE BODY as an OBJECT, the request must be in the following format :

{

‘name’: ‘sample’,

‘price’: 56,

‘isbn’:8965565

}

This POST request body is sent as a JSON object by the Client.

Defining the POST method, as folows:

def add_book(): #allows client to add a new entry

return jsonify(request.get_json()) #get the JSON request body that our client sent

Here, in order to test the req body sent to us, we return jsonify

Runnable Code Snippet_2.2- Getting the Request body sent by the Client:

# 2.2 Getting the Request body sent by the client

from flask import Flask, jsonify ,request #request library

#instance
app = Flask(__name__)
print(__name__)

#creating List with 2 dictionaries
books = [
	{
		'name' : 'Book1',
		'price' : 10,
		'isbn' : 987654
	},
	{
		'name' : 'Book2',
		'price' : 20,
		'isbn' : 123456
	}
]

#route decorators

#GET/books
@app.route('/books') 
def get_all_books():
	return jsonify({'Menu': books})

#in POST request,details of the new entry must be given by the Client
#in their RESPONSE BODY as an OBJECT
#the request looks should something like below
"""
{
	'name': 'sample',
	'price': 56,
	'isbn':8965565
}
"""
#Client send this as a JSON object


#adding POST request
@app.route('/books', methods=['POST']) #POST request will be fired
def add_book():			#allows client to add a new entry
	return jsonify(request.get_json()) #get the JSON request body that our client sent
	#in order to test the req body sent to us, we return jsonify

#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
	return_value={} #empty dictionary to hold ret_val
	for book in books:
		if book["isbn"] == isbn:
			return_value = {
				'name' : book["name"],
				'price' : book["price"]
			}
	return jsonify(return_value)		

app.run(port=5000)

2.3 Sending POST request to our Flask API using POSTMAN app

Postman download link is provided here:https://www.getpostman.com/downloads/

Route decorator and methods are same as above, now we are going to save and run the app.

Runnable Code Snippet_2.3- Sending a POST request:

# 2.3 Sending POST request to our Flask API using POSTMAN app

from flask import Flask, jsonify ,request 

#app instance
app = Flask(__name__)
print(__name__)

#creating List with 2 dictionaries
books = [
	{
		'name' : 'Book1',
		'price' : 10,
		'isbn' : 987654
	},
	{
		'name' : 'Book2',
		'price' : 20,
		'isbn' : 123456
	}
]

#route decorators

#GET/books
@app.route('/books') 
def get_all_books():
	return jsonify({'Menu': books})

#adding POST request
@app.route('/books', methods=['POST']) #POST request will be fired
def add_book():			#allows client to add a new entry
	return jsonify(request.get_json()) 

#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
	return_value={} #empty dictionary to hold ret_val
	for book in books:
		if book["isbn"] == isbn:
			return_value = {
				'name' : book["name"],
				'price' : book["price"]
			}
	return jsonify(return_value)		

app.run(port=5000)

Follow the given steps to fire a POST request using Postman

TO USE POSTMAN:

step1: set URL http://127.0.0.1:5000/books

step2: set request POST

step3: set Headers->KEY->Content-Type as application/json

step4: set Body->

{

“name”: “New Book1”,

“price”: 20,

“isbn”: 23434

}

step5: Hit Send button at the Right Top corner

step6: Check the Response window

->Headers->Content-Type: application/json

(i.e., we have got the req data our client sent) and status: 200 OK

Behind the Screen

Postman send a POST Request to our (server)books route and then our add_book() method ran, which got the request JSON, jsonified it, set the right content Headers back to us, and then sent that Response back (to postman)

2.4 Sanitizing Data sent in POST request

We may get some garbage data passed along with the request body. So it’s a good practice to Sanitize the data received from Client, as follows

we need to check if “keyName” is in the dictionaryObject

we define a valid object in the method as follows,

def validBookObject(bookObject):

if (“name” in bookObject and “price” in bookObject and “isbn” in bookObject):

return True

else:

return False

Runnable Code Snippet_2.4- Sanitizing Data sent in POST request:

# 2.4 Sanitizing Data sent in POST request

from flask import Flask, jsonify,request

app = Flask(__name__)
print(__name__)

books=[
	{
		'name' : 'Book1',
		'price' : 15.5,
		'isbn' : 123456
	},
	{
		'name' : 'Book2',
		'price' : 12.2,
		'isbn' : 789654
	}
]

#request body
#{
#	'name' : 'Newbook',
#	'price' : 456,
#	'isbn' : 369852
#}

		#Sanitize the data received from Client
#if "keyName" in dictionaryObject
def validBookObject(bookObject):
	if ("name" in bookObject and "price" in bookObject and "isbn" in bookObject):
		return True
	else:
		return False	

#Adding a POST route
#POST/books
@app.route('/books', methods=['POST'])
def add_book():
	return jsonify(request.get_json())

#GET/books
@app.route('/books')
def get_all_books():
	return jsonify({'Books':books})

#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
	return_value={}
	for book in books:
		if book["isbn"] == isbn:
			return_value={
				'name' : book["name"],
				'price' : book["price"]
			}
	return jsonify(return_value)

app.run(port=5000)

2.5 Testing how well we sanitized Invalid Request Bodies

We create a separate file called ‘DataSanitizer_TestCase.py’ to check how well we sanitized invalid request bodies being sent to us

Runnable Code Snippet_2.5- Testing how well we sanitized Invalid Request bodies:

# 2.5 Testing how well we sanitized Invalid Request Bodies

#DataSanitizer_TestCase.py
def validBookObject(bookObject):
	#if "keyName" in dictionaryObject
	if ("name" in bookObject 
			and "price" in bookObject 
				and "isbn" in bookObject):
		return	True
	else:
		return False

valid_object = {
	'name' : 'sample',
	'price' : 4.5,
	'isbn' : 963
}

missing_name = {
	'price' : 4.5,
	'isbn' : 963
}

missing_price = {
	'name' : 'sample',
	'isbn' : 963
}

missing_isbn = {
	'name' : 'sample',
	'price' : 4.5
}

empty_dictionary = {}

CLI Output

rs@rs-pc:~/RSflaskAPI$ python

Python 3.6.8 (default)

>>> from DataSanitizer_TestCase import *

>>> validBookObject(valid_object)

True

>>> validBookObject(missing_name)

False

>>> validBookObject(missing_price)

False

>>> validBookObject(missing_isbn)

False

>>> validBookObject(empty_dictionary)

False

>>> validBookObject(valid_object)

True

>>> exit()

rs@rs-pc:~/RSflaskAPI$

2.6 Adding New book to our Store using POST

#Adding a POST route

#POST/books

@app.route(‘/books’, methods=[‘POST’])

def add_book():

request_data = request.get_json()#get the req.data by get_json method

if(validBookObject(request_data)): #considering only validObject is sent

books.insert(0, request_data)#append bookObject we received from client,into BookList

return “True”

else:

return “False”

Runnable Code Snippet_2.6- Adding new book to our store using POST:

# 2.6 Adding New book to our Store using POST

from flask import Flask, jsonify,request

app = Flask(__name__)
print(__name__)

books=[
	{
		'name' : 'Book1',
		'price' : 15.5,
		'isbn' : 123456
	},
	{
		'name' : 'Book2',
		'price' : 12.2,
		'isbn' : 789654
	}
]

#request body format
#{
#	'name' : 'Newbook',
#	'price' : 456,
#	'isbn' : 369852
#}

#data sanitizer
def validBookObject(bookObject):
	#if entry already exists
	for i in books:
		if i["isbn"] == bookObject["isbn"]:
			return False
		else:
			#if "keyword" in dictionary	
			if ("name" in bookObject and "price" in bookObject and "isbn" in bookObject):
				return True
			else:
				return False	

#Adding a POST route
#POST/books
@app.route('/books', methods=['POST'])
def add_book():
	request_data = request.get_json()#get the req.data by get_json method 
	if(validBookObject(request_data)): #considering only validObject is sent
		books.insert(0, request_data)#append bookObject we received from client,into BookList
		return "True"
	else:
		return "False"

#GET/books
@app.route('/books')
def get_all_books():
	return jsonify({'Books':books})

#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
	return_value={}
	for book in books:
		if book["isbn"] == isbn:
			return_value={
				'name' : book["name"],
				'price' : book["price"]
			}
	return jsonify(return_value)

app.run(port=5000)

Using CLI:

Save and run the app in CLI

$ python RSapp.py

Steps to be followed and its expected output are given below:

Using Postman

step1:

GET request fired at http://127.0.0.1:5000/books

output:

list of all books in JSON

step2:

POST request fired at http://127.0.0.1:5000/books

with body

{

“name”: “New Book”,

“price”: 15.5,

“isbnx”: 123456

}

set header as Header -ContentType-application/json

and set body as Body -raw-JSON

RESPONSE:

True

step3:

GET request fired again at http://127.0.0.1:5000/books

output:

list of all books including the NewBook found in JSON

2.7 Mistake to be aware of when using POST in Flask

Incase, we get some garbage data along with the actualdata, we should not consider or take it.

Say, if we got a request body passed with some garbage data, as below

{

“name”: “New Book with some Garbage”,

“price”: 15.5,

“isbn”: 123456,

“garbage”: 8hhsu8*$$^#VGGE

}

We should take the required data only, garbage data must be ignored.

#POST/books

@app.route(‘/books’, methods=[‘POST’])

def add_book():

request_data = request.get_json()#get the req.data by get_json method

if(validBookObject(request_data)): #considering only validObject is sent

new_book = {

“name”: request_data[‘name’],

“price”: request_data[‘price’],

“isbn”: request_data[‘isbn’]

}

books.insert(0, new_book)#append new_bookObject we (sanitized)received from client,into BookList

return “True”

else:

return “False”

Runnable Code Snippet_2.7-Mistake to be aware while using POST in flask:

#Mistake to be aware while using POST in flask:

from flask import Flask, jsonify,request

app = Flask(__name__)
print(__name__)

books=[
	{
		'name' : 'Book1',
		'price' : 15.5,
		'isbn' : 123456
	},
	{
		'name' : 'Book2',
		'price' : 12.2,
		'isbn' : 789654
	}
]

#request body
#{
#	'name' : 'Newbook',
#	'price' : 456,
#	'isbn' : 369852
#}

#data sanitizer
def validBookObject(bookObject):
	#if entry already exists
	for i in books:
		if i["isbn"] == bookObject["isbn"]:
			return False
		else:
			#if "keyword" in dictionary	
			if ("name" in bookObject and "price" in bookObject and "isbn" in bookObject):
				return True
			else:
				return False	

#Adding a POST route
#POST/books
@app.route('/books', methods=['POST'])
def add_book():
	request_data = request.get_json()#get the req.data by get_json method 
	if(validBookObject(request_data)): #considering only validObject is sent
		new_book = {
			"name": request_data['name'],
			"price": request_data['price'],
			"isbn": request_data['isbn']
		}
		books.insert(0, new_book)#append new_bookObject we (sanitized)received from client,into BookList
		return "True"
	else:
		return "False"

#GET/books
@app.route('/books')
def get_all_books():
	return jsonify({'Books':books})

#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
	return_value={}
	for book in books:
		if book["isbn"] == isbn:
			return_value={
				'name' : book["name"],
				'price' : book["price"]
			}
	return jsonify(return_value)

app.run(port=5000)

2.8 Setting Status codes and Response bodies for POST requests

We returned “True” or return “False” strings/any strings in Flask and implicitly send a SAS code of “200 OK” back to the Client and content type of HTML back to the client as well.

In this case , we set “201” status code-> which indicates reuest has been fulfilled and resulted in one or more new resources being created.

Here we import ‘Response’ class, to invoke response constructor and set what is returned as response.

after inserting newbook to list, we add this Snippet

books.insert(0, new_book) #after this <-

Response{“par1″,”par2″,”par3”}

parameter 1-> Response body (we set this Empty)

parameter 2-> Status Code (we set this to 201)

parameter 3-> Content type header that will be sent back to Client(we send application/json and not HTML)

Once the Response constructor gets invoked, we save it to an Object called ‘response’ , as follows

response = Response{“par1″,”par2″,”par3”} #saved to Object

And instead of returning “True”, we return the response object itself

response = Response(“”,201,mimetype=’application/json’)

Finally we return the above response

Runnable Code Snippet_2.8-Setting Status codes and Response for POST request

#2.8 SettingStatuscodesandResponse

from flask import Flask, jsonify,request,Response

app = Flask(__name__)
print(__name__)

books=[
	{
		'name' : 'Book1',
		'price' : 15.5,
		'isbn' : 123456
	},
	{
		'name' : 'Book2',
		'price' : 12.2,
		'isbn' : 789654
	}
]

#request body
#{
#	'name' : 'Newbook',
#	'price' : 456,
#	'isbn' : 369852
#}


#data sanitizer
def validBookObject(bookObject):
	#if entry already exists
	for i in books:
		if i["isbn"] == bookObject["isbn"]:
			return False
		else:
			#if "keyword" in dictionary	
			if ("name" in bookObject and "price" in bookObject and "isbn" in bookObject):
				return True
			else:
				return False	

#Adding a POST route
#POST/books
@app.route('/books', methods=['POST'])
def add_book():
	request_data = request.get_json()#get the req.data by get_json method 
	if(validBookObject(request_data)): #considering only validObject is sent
		new_book = {
			"name": request_data['name'],
			"price": request_data['price'],
			"isbn": request_data['isbn']
		}
		books.insert(0, new_book)#append new_bookObject we (sanitized)received from client,into BookList
		response = Response("",201,mimetype='application/json')
		return response
	else:
		return "False"

#GET/books
@app.route('/books')
def get_all_books():
	return jsonify({'Books':books})

#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
	return_value={}
	for book in books:
		if book["isbn"] == isbn:
			return_value={
				'name' : book["name"],
				'price' : book["price"]
			}
	return jsonify(return_value)

app.run(port=5000)

2.9 Setting location Headers for POST requests in Flask

Location Header points to where the Client can go and receive the new Resource that was created. In Flask Headers works same as KeyValue pair.

Client receives access to a resource it created not from the actual response body, but from a Location Header, and thats why we set the response body EMPTY in the previous code

After we get the response, we set the headers and call it as response.headers[], which takes a string ‘Location’ and we explicitly set what we want to be the header, as follows

reponse.headers[‘Location’] = <URL with the newly added isbn>

URL format: /books/isbn

Note here we will be using a RELATIVE PATH here, because the Client already knows what the Server is because they reaching it,

So we get newbook’s isbn and convert it to string as follows

“/books” + str(new_book[‘isbn’])

We convert the isbn to a string, so that it concatenates correctly.

The header is set and response.headers[‘Location’] = “/books” + str(new_book[‘isbn’]) is sent as header.

Runnable Code Snippet_2.9-Setting location Headers for POST requests in Flask

#2.9-Setting location Headers for POST requests in Flask

from flask import Flask, jsonify,request,Response

app = Flask(__name__)
print(__name__)

books=[
	{
		'name' : 'Book1',
		'price' : 15.5,
		'isbn' : 123456
	},
	{
		'name' : 'Book2',
		'price' : 12.2,
		'isbn' : 789654
	}
]

#request body
#{
#	'name' : 'Newbook',
#	'price' : 456,
#	'isbn' : 369852
#}


#data sanitizer
def validBookObject(bookObject):
	#if entry already exists
	for i in books:
		if i["isbn"] == bookObject["isbn"]:
			return False
		else:
			#if "keyword" in dictionary	
			if ("name" in bookObject and "price" in bookObject and "isbn" in bookObject):
				return True
			else:
				return False	

#POST/books
@app.route('/books', methods=['POST'])
def add_book():
	request_data = request.get_json()#get the req.data by get_json method 
	if(validBookObject(request_data)): #considering only validObject is sent
		new_book = {
			"name": request_data['name'],
			"price": request_data['price'],
			"isbn": request_data['isbn']
		}
		books.insert(0, new_book)#append new_bookObject we (sanitized)received from client,into BookList
		response = Response("",201,mimetype='application/json')
		response.headers['Location'] = "/books/" + str(new_book['isbn'])
		return response
	else:
		return "False"

#GET/books
@app.route('/books')
def get_all_books():
	return jsonify({'Books':books})

#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
	return_value={}
	for book in books:
		if book["isbn"] == isbn:
			return_value={
				'name' : book["name"],
				'price' : book["price"]
			}
	return jsonify(return_value)

app.run(port=5000)

Postman Output after posting a body

{

“name”: “New Book”,

“price”: 15.5,

“isbn”: 64796

}

Response Window:

Location – http://127.0.0.1:5000/books/64796

We can see our LocationHeader has been updated

2.10 Handling invalid POST requests

Instead of returning “False” when invalid object is POST, we throw a meaningful Error Message and Status Code

invalidBookObjectErrorMsg = {

“error” : “Invalid book object in request”,

“helpString” : “Pls pass Data in similar format {‘name’:’bookname’, ‘price’:5.9, ‘isbn’:852569}”

}

response = Response(json.dumps(invalidBookObjectErrorMsg),status=400,mimetype=’application/json’)

return response

Runnable Code Snippet_2.10 -Handling invalid POST requests

#2.10 - Handling invalid POST request

from flask import Flask, jsonify,request,Response, json

app = Flask(__name__)
print(__name__)

books=[
	{
		'name' : 'Book1',
		'price' : 15.5,
		'isbn' : 123456
	},
	{
		'name' : 'Book2',
		'price' : 12.2,
		'isbn' : 789654
	}
]

#request body
#{
#	'name' : 'Newbook',
#	'price' : 456,
#	'isbn' : 369852
#}

#data sanitizer
def validBookObject(bookObject):
	#if entry already exists
	for i in books:
		if i["isbn"] == bookObject["isbn"]:
			return False
		else:
			#if "keyword" in dictionary	
			if ("name" in bookObject and "price" in bookObject and "isbn" in bookObject):
				return True
			else:
				return False	

#Adding a POST route
#POST/books
@app.route('/books', methods=['POST'])
def add_book():
	request_data = request.get_json()#get the req.data by get_json method 
	if(validBookObject(request_data)): #considering only validObject is sent
		new_book = {
			"name": request_data['name'],
			"price": request_data['price'],
			"isbn": request_data['isbn']
		}
		books.insert(0, new_book)#append new_bookObject we (sanitized)received from client,into BookList
		response = Response("",201,mimetype='application/json')
		response.headers['Location'] = "/books/" + str(new_book['isbn'])
		return response
	else:
		invalidBookObjectErrorMsg = {
			"error" : "Invalid book object in request",
			"helpString" : "Pls pass Data in similar format {'name':'bookname', 'price':5.9, 'isbn':852569}" 
		}
		response = Response(json.dumps(invalidBookObjectErrorMsg),status=400,mimetype='application/json')
		return response

#GET/books
@app.route('/books')
def get_all_books():
	return jsonify({'Books':books})

#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
	return_value={}
	for book in books:
		if book["isbn"] == isbn:
			return_value={
				'name' : book["name"],
				'price' : book["price"]
			}
	return jsonify(return_value)

app.run(port=5000)

Postman output is given below for reference,

Request body:

{

“name”: “Newbook”,

“price”: 123,

“isbnx”: 123

}

Status: 400 BAD REQUEST

Response body:

{

“error”: “Invalid book object in request”,

“helpString”: “Pls pass data in following format {‘name’:’Bookname’,’price’: 15.5,’isbn’: 456}”

}

So,…We have come to the end of this module, in the upcoming module we will deal with other HTTP methods to Update Data.

For Complete Sourcecode visit my Github repo: Link provided in the final module.

Rating: 3 out of 5.

RS-codes

2 thoughts on “First time with Flask-Python -Module2

Leave a comment

Design a site like this with WordPress.com
Get started