
To be updated soon… : )

To be updated soon… : )

Welcome back,
This is our sixth and final module of building our flask API. In this module, we will add authentication to our API. Main idea here is Clients need to send us a JSON Web Token (JWT), when requesting one of our APIs. Only if a client’s JWT is valid we grant access to our API. JWTs are encoded JSON objects that carry information of a client in between requests. JWTs typically include informations such as, when a client session expires, what algorithm used to encode a token, and client’s unique ID.
In this module, we will discuss the following:
6.ADDING AUTHENTICATION TO OUR API
6.1 Creating a JWT Tokens
6.2 Adding Authentication for our GET route
6.3 Creating a Database for Users who are allowed API access
6.4 Creating Users ,who are allowed API access
6.5 Having User Login to get a Token
6.6 Creating Decorators to Add API Authentication
6.7 Adding Authentication for all our routes using Decorators
By the end of this module, you will be able to
Lets get started..
JWT-JSON WebToken is a way to handle authentication of our API without having our client to send us a username and password over for every API call.
Sample JSON Web Token(JWT):
Sample JSON Web Token(JWT)
//Header
{
"alg": "HS256", #algorithm used to encode Token
"typ": "JWT"
}
//Payload
{
"id": 123456 #Client's unique id
"name": "Author RS"
"issued_at": 1516239022
"expiration_date": 1516939022 #when Client session expires
}
First thing first, import JWT and define a secret key in our app object. Define a route that allow someone to get a JWT token. For time being, we’re going to allow anybody to get this token, we’ll add actual authentication sooner. Define a route called login and within this define a method called get_token, which returns a token back to whoever calls this route. The jwt library has an encode method, make all the route to call the jwt encode method and return back a token to us. Call encode method, it takes in few parameters, the first parameter is the expiration date of this token, which is a dictionary object that has a key exp and the value an actual expiration_date . The second parameter is the actual SECRET_KEY that we have defined. The third parameter is the algorithm used for encoding, we set this to a default algorithm- HS256.
Define expiration_date. For that import the datetime library, we get the current time from this library, add a delta to that time. Here we add 100 seconds i.e., our token will last 100 seconds from the time client requested it.
In app.py add the following snippet
Code Snippet_6.1-Creating a JWT(JSON WebToken) Tokens
# In app.py add the following snippet:
import jwt, datetime #import jwt library which has JWT encode method
app.config['SECRET_KEY'] = 'spinach' #give any secretkey of ur choice
@app.route('/login')
def get_token():
expiration_date = datetime.datetime.utcnow() + datetime.timedelta(seconds=100)
token = jwt.encode({'exp': expiration_date}, app.config['SECRET_KEY'],algorithm='HS256')
return token
TERMINAL:
Open up the terminal, start our server.From Postman fire a GET request. In the Response body you will get the token generated.
#TERMINAL:
Run server up
#POSTMAN:
#fire GET request at http://127.0.0.1:5000/login
#RESPONSE BODY:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NzQ5MzA4MTN9.TCtCrIbGhzuCOZipvxN3ZLnNd9XhjKtRmh_Y_G8ChwI #<=token generated
If anyone wants to view our book collection using our books route, then they need to pass us a valid token in order to view this resource. Instead of our client just passing in a GET request of books like before, now they have to send us in token using a query parameter. We have to parse the query parameters that are passed in. Run the jwt.decode method. The jwt.decode method throw an exception if token is invalid. For example, if the token that’s passed in to our client expired, or empty, or if the SECRET_KEY is wrong, then jwt.decode will throw an exception. Return a JSON response for error, and give a help string, say ‘Need a valid token to view this page’. If a valid token is passed, then return the jsonify of the books.
In app.py add the following snippet:
#In app.py import and add the following snippet:
import jwt, datetime #import jwt library which has JWT encode method
app.config['SECRET_KEY'] = 'spinach'
@app.route('/login')
def get_token():
expiration_date = datetime.datetime.utcnow() + datetime.timedelta(seconds=100)
token = jwt.encode({'exp': expiration_date}, app.config['SECRET_KEY'],algorithm='HS256')
return token
In app.py edit decorators, as follows:
# In app.py edit decorators
#GET/books?token=asok9f09rtwi978hs
@app.route('/books')
def get_all_books():
token = request.args.get('token')
try:
jwt.decode(token, app.config['SECRET_KEY'])
except:
return jsonify({'error': 'Need a valid token'}),401
return jsonify({'Books':Book.get_all_books()}) #from database
Runnable Code Snippet_6.2-Adding Authentication for our GET route:
#RSapp.py
from flask import Flask, jsonify,request,Response
from BookModel import *
from settings import *
import json #import Json
from settings import * #import all
import jwt, datetime #import jwt library which has JWT encode method
app.config['SECRET_KEY'] = 'spinach'
@app.route('/login')
def get_token():
expiration_date = datetime.datetime.utcnow() + datetime.timedelta(seconds=100)
token = jwt.encode({'exp': expiration_date}, app.config['SECRET_KEY'],algorithm='HS256')
return token
#request body
#{
# 'name' : 'Newbook',
# 'price' : 456,
# 'isbn' : 369852
#}
#Sanitize the data received from Client
#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
Book.add_book(request_data['name'], request_data['price'], request_data['isbn'])
response = Response("", status=201, mimetype='application/json')
response.headers['Location'] = "/books" + str(request_data['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?token=a9f09rtwi978hs
@app.route('/books')
def get_all_books():
token = request.args.get('token')
try:
jwt.decode(token, app.config['SECRET_KEY'])
except:
return jsonify({'error': 'Need a valid token'}),401
return jsonify({'Books':Book.get_all_books()}) #from database
#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
return_value=Book.get_by_isbn(isbn)
return jsonify(return_value)
#PUT /books/5869
#{
# 'name': 'NewName',
# 'price': 456
#}
def valid_put_request_data(request_data):
if ("name" in request_data
and "price" in request_data):
return True
else:
return False
#PUT route
@app.route('/books/<int:isbn>', methods=['PUT'])
def replace_book(isbn):
request_data=request.get_json()
if(not valid_put_request_data(request_data)):
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
Book.replace_book(isbn, request_data['name'],request_data['price'])
response = Response("", status=204) #No content created
return response
# PATCH /books/isbn
#{
# 'name': 'UpdateNameAlone'
#}
# PATCH /books/isbn
#{
# 'price': 'UpdatePriceAlone'
#}
def valid_patch_request_data(request_data):
if ("name" in request_data
or "price" in request_data):
return True
else:
return False
@app.route('/books/<int:isbn>', methods=['PATCH'])
def update_book(isbn):
request_data = request.get_json() #we get the JSON data
if(not valid_patch_request_data(request_data)):
invalidBookObjectErrorMsg = {
"error": "Invalid book object passed in request",
"helpstring": "Data should be in following format{'name': 'bookname', 'price':15}"
}
response = Response (json.dumps(invalidBookObjectErrorMsg),staus=404,mimetype='application/json')
return response
if("name" in request_data):
Book.update_book_name(isbn, request_data['name'])
if("price" in request_data):
Book.update_book_price(isbn, request_data['price'])
response = Response("",status=204) #204=Success
response.headers['Location'] = "/books/" + str(isbn)
return response
#DELETE /books/isbn
@app.route('/books/<int:isbn>', methods=['DELETE'])
def delete_book(isbn):
if(Book.delete_book(isbn)):
response = Response("",status=204)
return response
invalidBookObjectErrorMsg = {
"error":"Unable to Delete,as ISBN provided doesnot found in the List"
}
response = Response(json.dumps(invalidBookObjectErrorMsg),status=404, mimetype='application/json') #Status404:Resource not found
return response
app.run(port=5000)
Open Terminal and run our server.
TERMINAL:
#TERMINAL:
Run server up
POSTMAN:
From POSTMAN fire a GET request to the books collection without a token, we will get an error that says we need a valid token to view this page.
#POSTMAN:
fire GET /books
#RESPONSE WINDOW:
{
"error": "Need a valid token"
}
#STATUS: 401 UNAUTHORIZED
Now do a GET request to our log in route(GET/at http://127.0.0.1:5000/login), and what this will do is it’ll return us a token that is valid for 100 seconds. If you pass that in to this GET request and do a Send, you can see the collection in response.
#POSTMAN:
fire GET/at http://127.0.0.1:5000/login
#RESPONSE WINDOW:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NzQ5MzMxOTh9.uYmJYg5ufk2iKzv6Am5GA2G29hmo1fwaidRwVsvuuwg
#above token generated
fire GET/at http://127.0.0.1:5000/books?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NzQ5MzMxOTh9.uYmJYg5ufk2iKzv6Am5GA2G29hmo1fwaidRwVsvuuwg
#RESPONSE WINDOW:
{
"Books": [
{
"isbn": 753357,
"name": "NewBook1",
"price": 3.5
}
]
}
#STATUS 200 OK
You can check by modifying the token manually and send that, you will see the error: need a valid token to view this page, again.
So far, we learned how to set up and create a JWT token. We were able to do things, such as add an expiration date to a token, as well as any other data we want to obtain between API calls. We also have the ability to receive tokens from requests, and determine whether the tokens to be received really are valid and authenticated. The goal now is to give tokens only to users who are authenticated. That way, only people we intend to use our API can access.
To do this create a new user table in our database that will store username and password. Whenever a client sends us a given username and password to our login route, we will check if the username and password combination exists in our database. If so, we will return to the client a token they can use. We will be using plain text passwords in our database and creating a basic user model to show you the inner details of how Flask integrates with JSON Web Tokens. On a production environment, we use an authentication library instead of coding everything from scratch. This is because libraries handle things, such as security and encoding passwords for us.
Lets code now, create a file called UserModel.py. Define a user model that can map a Python class that will create virtual users to an actual SQL table. And then create the initial database. This is very similar to what we did in the last module. Create db object, and this db object actually requires a couple of dependencies, and the very first one is the app object, which we import from our settings.py, and then the second thing is SQLAlchemy. From flask_sqlalchemy, import sqlalchemy. Define the model, which is basically a Python class that we’ll map to a SQL table. For this user class, we’ll inherit from the db.Model a declarative base class. And our User class will behave just like any other Python class, but it allows us to inherit a query method that could be used to run all types of searches in the database. This inherited query method allows us to access the SQL database and get all the data we need. Now, define the tablename as users and create columns.
Note: if we don’t define tablename, the class name that we use will be converted into a tablename. And there is a conversion process that happens, but we want to avoid that, which is why we are explicitly defining the tablename here.
Now specify the columns. The very first column we need is the ID, and this will act as our primary key set the primary_key property to be True and type Integer, use the same column method from the db object as we did before. The second column will hold the username define this as a string and it must be unique,so set unique equal to True. Make it non-nullable, by setting nullable=False. And the third column we have will hold the password, which is also a column that holds a string, same as the username make this password non-nullable. Now, let’s create our database.
Code Snippet_6.3-Creating a Database for Users who are allowed API access:
UserModel.py
#UserModel.py
from flask_sqlalchemy import SQLAlchemy
from settings import app
db = SQLAlchemy(app)
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(80), nullable=False)
TERMINAL:
Open the terminal and call Python. Now from our UserModel import everything and call the db.create_all method. Exit and do a cat of our database.db, we can see our user table and our books table, and we can see that user class we defined with a username, a password, the previous books class and the data that we currently have.
#TERMINAL:
$ ls
BookModel.py DataSanitizer_TestCase.py RSapp.py
database.db __pycache__ settings.py
$ touch UserModel.py #create a file
$ls
BookModel.py DataSanitizer_TestCase.py RSapp.py UserModel.py
database.db __pycache__ settings.py
$ subl UserModel.py #open file and paste the snippet given above
After coding the UserModel.py file, as instructed above.
#TERMINAL:
$ python
Python 3.6.8 (default)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from UserModel import *
>>> db.create_all()
>>> exit()
$ ls
BookModel.py DataSanitizer_TestCase.py RSapp.py UserModel.py
database.db __pycache__ settings.py
$ cat database.db
tableusers#CREATE TABLE users ( #<==userDB
id INTEGER NOT NULL, #<==newly created tables/columns
username VARCHAR(80) NOT NULL,
password VARCHAR(80) NOT NULL,
PRIMARY KEY (id),
UNIQUE (username)
))indexsqlite_autoindex_users_1users
#tablebooksbooks#CREATE TABLE books ( #<=books db
id INTEGER NOT NULL,
name VARCHAR(80) NOT NULL,
price FLOAT NOT NULL,
isbn INTEGER,
PRIMARY KEY (id)
NewBook1 #<=content
:
:
Define a username_password_match function which takes a username and a password passed in, this method will be used as an API in our route to determine whether someone should get access or not, define this method to return a Boolean, if we find a match for the username and password combination that was sent to us, then we’ll get back a user object, otherwise if we don’t find a match we’ll get a non-user response. Define a getAllUsers method, this won’t be used publicly within the user API but helps us to test. Define new_user using our User constructor, add this to our database and commit this.
Runnable Code Snippet_6.4- Creating Users ,who are allowed API access:
# 6.4 Creating Users ,who are allowed API access
#UserModel.py
from flask_sqlalchemy import SQLAlchemy
from settings import app
db = SQLAlchemy(app)
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(80), nullable=False)
#printable representation of a given object
def __repr__(self):
return str({
'username': self.username,
'password': self.password
})
#username/password match ftn,
#acts as API to determine whether someone should get access
def username_password_match(_username, _password):
user = User.query.filter_by(username=_username).filter_by(password=_password).first()
if user is None:
return False
else:
return True
#getAllUsers method
def getAllUsers():
return User.query.all() #returns in the representation format given at top
#createUser method
def createUser(_username, _password):
new_user = User(username=_username, password=_password)
db.session.add(new_user)
db.session.commit()
TERMINAL:
Save the file, open a terminal and call Python. Now import everything from our UserModel. First get all the users to make sure that we get an empty list. Now check username_password_match by passing it different username and password, which should return false. Let’s create a user named user1, set its password to password. If we run the user.getAllUsers, we should see our new user that was created. Now check whether our username_password_match function actually works by passing it in user1 as username, and password as password this should return True, as expected.
#TERMINAL:
$ python
Python 3.6.8 (default)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from UserModel import *
>>> User.getAllUsers()
[] #<== empty
>>> User.username_password_match('a', 'b')
False
>>> User.createUser("user1", "password1")
>>> User.getAllUsers()
[{'username': 'user1', 'password': 'password1'}]
>>> User.username_password_match('user1', 'password1')
True
>>> User.username_password_match('user1', 'pass1word')
False
>>>
Let’s use our UserModel API to validate that a given username and password passed in to our login route exists in our database. If it exists, we give client a JSON Web Token, otherwise return an unauthorized message back to the client. Edit the code as follows.
Code edit in app.py:
# 6.5 Having User Login to get a Token
#app.py
from UserModel import User #import User
#and edit the route directory as POST and add this snippet
@app.route('/login',methods=['POST'])
def get_token():
request_data = request.get_json()
username = str(request_data['username'])
password = str(request_data['password'])
match = User.username_password_match(username, password)
if match:
expiration_date = datetime.datetime.utcnow() + datetime.timedelta(seconds=100)
token = jwt.encode({'exp': expiration_date}, app.config['SECRET_KEY'],algorithm='HS256')
return token
else:
return Response('', 401, mimetype='application/json')
Runnable Code Snippet_6.5 Having User Login to get a Token:
#app.py
from flask import Flask, jsonify,request,Response
from BookModel import *
from settings import *
import json #import Json
from settings import * #import all
import jwt, datetime #import jwt library which has JWT encode method
from UserModel import User #import User
app.config['SECRET_KEY'] = 'spinach'
@app.route('/login',methods=['POST'])
def get_token():
request_data = request.get_json()
username = str(request_data['username'])
password = str(request_data['password'])
match = User.username_password_match(username, password)
if match:
expiration_date = datetime.datetime.utcnow() + datetime.timedelta(seconds=100)
token = jwt.encode({'exp': expiration_date}, app.config['SECRET_KEY'],algorithm='HS256')
return token
else:
return Response('', 401, mimetype='application/json')
#request body
#{
# 'name' : 'Newbook',
# 'price' : 456,
# 'isbn' : 369852
#}
#Sanitize the data received from Client
#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
Book.add_book(request_data['name'], request_data['price'], request_data['isbn'])
response = Response("", status=201, mimetype='application/json')
response.headers['Location'] = "/books" + str(request_data['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?token=asok9f09rtwi978hs
@app.route('/books')
def get_all_books():
token = request.args.get('token')
try:
jwt.decode(token, app.config['SECRET_KEY'])
except:
return jsonify({'error': 'Need a valid token'}),401
return jsonify({'Books':Book.get_all_books()}) #from database
#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
return_value=Book.get_by_isbn(isbn)
return jsonify(return_value)
#PUT /books/5869
#{
# 'name': 'NewName',
# 'price': 456
#}
def valid_put_request_data(request_data):
if ("name" in request_data
and "price" in request_data):
return True
else:
return False
#PUT route
@app.route('/books/<int:isbn>', methods=['PUT'])
def replace_book(isbn):
request_data=request.get_json()
if(not valid_put_request_data(request_data)):
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
Book.replace_book(isbn, request_data['name'],request_data['price'])
response = Response("", status=204) #No content created
return response
# PATCH /books/isbn
#{
# 'name': 'UpdateNameAlone'
#}
# PATCH /books/isbn
#{
# 'price': 'UpdatePriceAlone'
#}
def valid_patch_request_data(request_data):
if ("name" in request_data
or "price" in request_data):
return True
else:
return False
@app.route('/books/<int:isbn>', methods=['PATCH'])
def update_book(isbn):
request_data = request.get_json() #we get the JSON data
if(not valid_patch_request_data(request_data)):
invalidBookObjectErrorMsg = {
"error": "Invalid book object passed in request",
"helpstring": "Data should be in following format{'name': 'bookname', 'price':15}"
}
response = Response (json.dumps(invalidBookObjectErrorMsg),staus=404,mimetype='application/json')
return response
if("name" in request_data):
Book.update_book_name(isbn, request_data['name'])
if("price" in request_data):
Book.update_book_price(isbn, request_data['price'])
response = Response("",status=204) #204=Success
response.headers['Location'] = "/books/" + str(isbn)
return response
#DELETE /books/isbn
@app.route('/books/<int:isbn>', methods=['DELETE'])
def delete_book(isbn):
if(Book.delete_book(isbn)):
response = Response("",status=204)
return response
invalidBookObjectErrorMsg = {
"error":"Unable to Delete,as ISBN provided doesnot found in the List"
}
response = Response(json.dumps(invalidBookObjectErrorMsg),status=404, mimetype='application/json') #Status404:Resource not found
return response
app.run(port=5000)
TERMINAL:
Fired requests from Postman and the expected outputs are given below.
#TERMINAL
$ python app.py
* Serving Flask app "settings"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - "GET /books HTTP/1.1" 401 - UNAUTHORIZED#<=tried entering /books route without any credentials
127.0.0.1 - "GET /login HTTP/1.1" 405 -METHOD NOT ALLOWED <=#<=tried entering /login route without any credentials
127.0.0.1 - "POST /login HTTP/1.1" 200 -OK #<==req body with a valid user {"username": user1, "password": password1) which is set in db already, and with contentType:json
#Got token generated
127.0.0.1-"GET/books?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NzQ5Mzc4NDB9.tvGlQ1yq1wfeLKUk-Ey1vO8U9NvosUC2LiqM0pUBfOw HTTP/1.1" 200 - OK #<=passed in token
127.0.0.1 -"POST /login HTTP/1.1" 401 - #<=gave invalid user in req body {"user": user2, "password": password1)
127.0.0.1-"GET/books?token=eyJ0eXAiOiJKV1QiLCJ1NiJ9.eyJleHAiOjE1NzQ5Mzc4NDB9.tvGlQ1yq1wfeLKUk-Ey1vO8U9NvosUC2LiqM0pUBfOw HTTP/1.1" 401 - #<==gave random token value
^C #stopped
Here we are going to migrate a lot of get_books code over and generalize it so that it can be used by any route. Very first thing we need to do is define our decorator, we define it as token_required which takes a function. Import wraps from functools, define our wrapper method, and this will take in the arguments and the keywords, and this wrapper method is going to replace the function that is being decorated. Get token from the request.args, try with this token to decode it if we get an exception return an error. Call @wraps. The code to be migrated and generalised is shown below.
Code Snippet -migrating and generalising:
#migrating and generalising so that it can be used by any route
#wrapper method replaces the function being decorated
from functools import wraps #for method token_required
def token_required(f):
@wraps(f)
def wrapper(*args, **Kwargs): #wrapper method takes arguments and keywords
token = request.args.get('token')
try:
jwt.decode(token, app.config['SECRET_KEY'])
except:
return jsonify({'error': 'Need a valid token'}),401
return wrapper
#GET/books?token=ak9f978hs
@app.route('/books')
@token_required
def get_all_books():
Runnable Code Snippet_6.6-Creating Decorators to Add API Authentication:
#app.py
from flask import Flask, jsonify,request,Response
from BookModel import *
from settings import *
import json #import Json
from settings import * #import all
import jwt, datetime #import jwt library which has JWT encode method
from UserModel import User #import User
from functools import wraps #for method token_required
app.config['SECRET_KEY'] = 'spinach'
@app.route('/login',methods=['POST'])
def get_token():
request_data = request.get_json()
username = str(request_data['username'])
password = str(request_data['password'])
match = User.username_password_match(username, password)
if match:
expiration_date = datetime.datetime.utcnow() + datetime.timedelta(seconds=100)
token = jwt.encode({'exp': expiration_date}, app.config['SECRET_KEY'],algorithm='HS256')
return token
else:
return Response('', 401, mimetype='application/json')
#request body
#{
# 'name' : 'Newbook',
# 'price' : 456,
# 'isbn' : 369852
#}
#Sanitize the data received from Client
#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
Book.add_book(request_data['name'], request_data['price'], request_data['isbn'])
response = Response("", status=201, mimetype='application/json')
response.headers['Location'] = "/books" + str(request_data['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
#wrapper method replaces the function being decorated
def token_required(f):
@wraps(f)
def wrapper(*args, **kwargs): #wrapper method takes arguments and keywords
token = request.args.get('token')
try:
jwt.decode(token, app.config['SECRET_KEY'])
return f(*args, **kwargs)
except:
return jsonify({'error': 'Need a valid token'}),401
return wrapper
#GET/books?token=asok9f09rtwi978hs
@app.route('/books')
@token_required
def get_all_books():
return jsonify({'Books':Book.get_all_books()}) #from database
#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
return_value=Book.get_by_isbn(isbn)
return jsonify(return_value)
#PUT /books/5869
#{
# 'name': 'NewName',
# 'price': 456
#}
def valid_put_request_data(request_data):
if ("name" in request_data
and "price" in request_data):
return True
else:
return False
#PUT route
@app.route('/books/<int:isbn>', methods=['PUT'])
def replace_book(isbn):
request_data=request.get_json()
if(not valid_put_request_data(request_data)):
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
Book.replace_book(isbn, request_data['name'],request_data['price'])
response = Response("", status=204) #No content created
return response
# PATCH /books/isbn
#{
# 'name': 'UpdateNameAlone'
#}
# PATCH /books/isbn
#{
# 'price': 'UpdatePriceAlone'
#}
def valid_patch_request_data(request_data):
if ("name" in request_data
or "price" in request_data):
return True
else:
return False
@app.route('/books/<int:isbn>', methods=['PATCH'])
def update_book(isbn):
request_data = request.get_json() #we get the JSON data
if(not valid_patch_request_data(request_data)):
invalidBookObjectErrorMsg = {
"error": "Invalid book object passed in request",
"helpstring": "Data should be in following format{'name': 'bookname', 'price':15}"
}
response = Response (json.dumps(invalidBookObjectErrorMsg),staus=404,mimetype='application/json')
return response
if("name" in request_data):
Book.update_book_name(isbn, request_data['name'])
if("price" in request_data):
Book.update_book_price(isbn, request_data['price'])
response = Response("",status=204) #204=Success
response.headers['Location'] = "/books/" + str(isbn)
return response
#DELETE /books/isbn
@app.route('/books/<int:isbn>', methods=['DELETE'])
def delete_book(isbn):
if(Book.delete_book(isbn)):
response = Response("",status=204)
return response
invalidBookObjectErrorMsg = {
"error":"Unable to Delete,as ISBN provided doesnot found in the List"
}
response = Response(json.dumps(invalidBookObjectErrorMsg),status=404, mimetype='application/json') #Status404:Resource not found
return response
app.run(port=5000)
TERMINAL:
Open up Terminal and Postman. Go to our books route and check that it ask for a token and ensure. Now go into our login route and pass in our username and password, now we should get a token back. Send this token over to get the book collection back.
#TERMINAL:
$ python app.py
* Serving Flask app "settings"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - "GET /books HTTP/1.1" 401 -#<=without credentials
127.0.0.1 - "POST /login HTTP/1.1" 200 -#<=with credentials #we get token generated
127.0.0.1-"GET/books?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NzQ5NDEyNzN9.KPLNPo98JAlpNwwwvU7_qjXnNSWnlFeX13x-EUqkGsU HTTP/1.1" 200 - #<=entered with token
127.0.0.1-"GET/books?token=eyJ0eXAiOiJKV1QiLCJhHAiOjE1NzQ5NDEyNzN9.KPLNPo98JAlpNwwwvU7_qjXnNSWnlFeX13x-EUqkGsU HTTP/1.1" 401 - #<= tried with random token
127.0.0.1 -"POST /login HTTP/1.1" 401 -#<=tried with random credential
^C #stopped
Now we add wrapper infront of all the methods and check authentication. We defined a decorator that allows us to acquire an access token for a client if they want to use any part of our API, but we made it so that GET requests of a resource require an access token, but typically for rest APIs we allow read access to resources, but restrict write access. We edit this now and make it so any POST, PUT, or PATCH request requires a token, while any GET request does not. So remove the token_required method from the get_books route and put token_required method in PUT, PATCH and DELETE routes.
Hope you can test this on your own, if you have followed all the modules.Please try it and let me know.
With this we have come to the end of this writeup, so far we added authentication to our API. We learnt how to create and validate tokens to only allow clients who have a valid token to be able to gain access to our resource. We then made it so clients actually needed to register in order to use our API. When a client tries to log in, their credentials would be checked against the database, and only if they have an account they will be able to get a token that will allow them to use our API. We then learnt how to use decorators to abstract all this functionality into one line of code.
To sum up this course., we started from the basics and created our first Hello World Flask application. From there, we then built out routes for GET, POST, PUT, PATCH, and DELETE requests. Once all that was done, we then migrated all of our data to a database and used Flask’s SQLAlchemy as our ORM. We covered the basics and foundations of JWT. Ultimately we had fun writing some Flask code : )
Thank you all and visit my other writeups and suggest me for any improvements.
love,
<R.S>
For Complete Sourcecode visit my Github repo
⭐⭐⭐⭐
Rating: 4 out of 5.
RS-codes

Welcome back,
This is our fifth module of building our flask API. In the last module, we finished up the DELETE route. So far we restored things locally within our Python server using a books list, and we were not pulling things from a database, therefore whenever we restart the server the net result of this will reset to whatever we hard-coded in our list on the server and all the actions performed such as deletions, additions that were made by client will be reset. This is not an expected way of approach, so in this module, we will change that and we will migrate our book list from being a static list into actually being pulled from a SQL database.
In this module, we will discuss the following:
5.STORING DATA IN SQL DB
5.1 Configuring a Flask SQLAlchemy Database
5.2 Defining Tables and Columns in Flask SQLAlchemy
5.3 Creating the Database
5.4 Defining a Data model with Flask SQLAlchemy
5.5 Updating our Data Model to Query our Database
5.6 Updating our Data Model to Delete Entries from our Database
5.7 Updating our Data Model to Update Table Entries
5.8 Updating our Data Model to Replace Table Entries
5.9 Returning back JSON from our Data Model
5.10 Updating our Flask App to use Our Book Model
5.11 Verifying the Book Model integration
Lets start..
Inorder to do this, we create a ‘settings.py’ file separately to store the settings of our application and configuration of our database. Create an app object .It has a config, and this config takes properties where certain things will be stored. The very first property will be the SQLALCHEMY_DATABASE_URI, and this is the path where we’ll store our database. We’ll be using sqllite, and we set the database path to be within our project directory. i.e., we are going to create a database.db file there. We need to add another config. And this will be the SQLALCHEMY_TRACK_MODIFICATIONS, we set this equal to false. Now these are all the settings that we need, and the main one is the first one where we define the path locally where our database will be stored.
settings.py
#settings.py
from flask import Flask
#Flask Constructor to create app Object
app = Flask(__name__) #this app Object is shared with RSapp.py file
#configuration to store SQLALCHEMY_DATABASE_URI,this will be the path where we'll store our database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////home/RSflaskAPI/database.db'
#Note: My present working directory path
# $pwd: /home/RSflaskAPI(defined the path locally to store DB, inside my project directory)
# you can enter your own project directory path
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
After this we need to create our app.py file, but we already have built it. So just edit it for our specific requirement.
app.py
One thing to note here is, we already have an app object in our settings.py file, so we share it from the settings.py file. Remove this from app.py and rest are same.
#RSapp.py
from settings import * #import all from settings.py <=add this
#app = Flask(__name__) #since we share the Object from settings.py <=remove this
We need to create a ‘BookModel.py’ file and import SQLAlchemy.
from flask_sqlalchemy import SQLALchemy
Note: you may need to install sqlalchemy, you can check manually using $pip freeze. Install using $pip install flask_sqlalchemy, if not installed already
then import app Object specifically from settings.py
from settings import app #importing app Object specifically
Create a database Object
db = SQLAlchemy(app) #calling SQLAlchemy Constructor and passing it the app Object
Create a class Book, which inherits from db.Model
class Book(db.Model):
Now defined the tablename
__tablename__='books' #this will be the name of the table that adds information to
We already have the DB name defined in our settings.py file, so now we define the Columns. Our books have three properties- name, price and isbn) and for SQL we need to have id as well, so we define 3+1= 4 columns.
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False) #cant be NULL
price = db.Column(db.Float, nullable=False)
isbn = db.Column(db.Integer)
SQLAlchemy will map the above variables into actual columns within the SQL DB db.Column from SQLAlchemy. Constructor takes few parameters, 1st parameter is the datatype and other parameters are OPTIONAL.
Code Snippet_5.2-Defining Tables and Columns in Flask SQLAlchemy:
BookModel.py
#BookModel.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import json
from settings import app #import app Object specifically from settings.py
#creating a database Object
db = SQLAlchemy(app) #calling SQLAlchemy Constructor and passing it the app Object
#class Book inherit from db.Model
class Book(db.Model):
#define tablename
__tablename__='books'
#define columns
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False) #cant be NULL
price = db.Column(db.Float, nullable=False)
isbn = db.Column(db.Integer)
#skipped db definition, since its done in settings.py
We are going to create our database and our tables and columns.
THIS MUST BE DONE ONLY ONCE, as we create the DB once.
Before creating a DB file check the contents of your current project directory. Showing my project directory content below, Note: we don’t have any DB file inside, as of now.
TERMINAL:
$ ls
BookModel.py DataSanitizer_TestCase.py __pycache__ RSapp.py settings.py
Open up Python. Within here, import our BookModel from BookModel import db. Now that we have this db variable in scope, call the db.create_all method. Once it is executed a database file will be created in our current project directory.
TERMINAL:
$ python
Python 3.6.8 (default)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from BookModel import db
>>> db.create_all() #database created
>>> exit()
Check by doing an ls, and you should see that there is a new database.db file here, if you have followed the above steps without any error.
$ ls
BookModel.py DataSanitizer_TestCase.py RSapp.py
database.db #<== Newly created database.db file
__pycache__ settings.py
and now that this database.db file is created, we can see the contents of it by giving cat command, [Note: cat is a Linux operation that you can do to read the contents of a file.] If we do this, you can see that the class that we defined here, and the book, and the tablename.
$ cat database.db
tablename books#CREATE TABLE books (
id INTEGER NOT NULL,
name VARCHAR(80) NOT NULL,
price FLOAT NOT NULL,
isbn INTEGER,
PRIMARY KEY (id)
Define a method called add_book, which takes in a name, a price and an ISBN. Call our book constructor and pass it _name, _price and _isbn this represents what was passed into the function.
def add_book(_name, _price, _isbn):
new_book=Book(name=_name, price=_price, isbn=_isbn)
Within our database session, we add this book, and we need to assign the resulting object that’s created into this new book and add it. This theoretically should add a book to the database, but we need a way to actually test to make sure that this works. So, we define another method that will get all the books in our database, then we add a book, and then call the get_all_books method, now we can see the new book that was added. Inherit Book class from the db.Model, it inherits a lot of special functionality, and just utilize it. The db object has a query method, so do our Book class also will have this query method, we can use this to query our database for all the results that we have. Define the book_object, and return a json representation of it where the name will be the current book object’s name, the price will be the current object’s price, and the ISBN will be the current object’s ISBN and return this.
Code Snippet_5.4-Defining a Data model with Flask SQLAlchemy:
#BookModel.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import json
from settings import app #import app Object specifically
#creating a database Object
db = SQLAlchemy(app) #calling SQLAlchemy Constructor and passing it the app Object
#class Book inherit from db.Model
class Book(db.Model):
#define tablename
__tablename__ = 'books'
#define columns
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False) #cant be NULL
price = db.Column(db.Float, nullable=False)
isbn = db.Column(db.Integer)
def add_book(_name, _price, _isbn):
new_book=Book(name=_name, price=_price, isbn=_isbn)
db.session.add(new_book)
db.session.commit()
def get_all_books():
return Book.query.all()
def __repr__(self):
book_object={
'name': self.name,
'price': self.price,
'isbn': self.isbn
}
return json.dumps(book_object)
Save this file and open up the terminal and test to make sure that everything is working. First thing is open up Python, then import our BookModel. We’re going to use this API from our app.py file. Call the Book.add_book and then pass a book name, a price and an ISBN number. But we don’t actually know if this worked or not. So to make sure that it worked, call the get_all_books method that we defined. The book we added will now be returned to us.
TERMINAL:
EXPECTED CLI OUTPUT:
$ cd RSflaskAPI
$ ls
BookModel.py DataSanitizer_TestCase.py RSapp.py
database.db __pycache__ settings.py
$ python
Python 3.6.8 (default)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from BookModel import *
>>> Book.add_book("NewBook",9.9,753357)
>>> Book.get_all_books()
[{"name": "NewBook", "price": 9.9, "isbn": 753357}]
>>> Book.add_book("NewBook2",1.3,432156)
>>> Book.get_all_books()
[{"name": "NewBook", "price": 9.9, "isbn": 753357}, {"name": "NewBook2", "price": 1.3, "isbn": 432156}]
>>>
Now lets define the ability to get a book by its ISBN number, for that define a get_book method, which takes in an ISBN passed to it, and then it’ll return the results. Whenever we search for things within our database, we call the Book.query method but this time instead of chaining the query to all, we chain it to a filter_by method which runs a query, we can set it to filter_by isbn and then we pass in the isbn to filter_by, this method takes a parameter and this parameter should match the column that we defined, this query should return only one ISBN, since the ISBNs are unique primary keys. To sum up what we are doin, we used SQLALCHEMY, query method that was inherited from this db.model, call Book.query and then do a bunch of chain commands. We run the query, and then we’re chaining it to what filter we want and then we’re chaining it to only give the first result.
Code Snippet_5.5- Updating our Data Model to Query our Database
# 5.5 Updating our Data Model to Query our Database
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import json
from settings import app #import app Object specifically
#creating a database Object
db = SQLAlchemy(app) #calling SQLAlchemy Constructor and passing it the app Object
#class Book inherit from db.Model
class Book(db.Model):
#define tablename
__tablename__ = 'books'
#define columns
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False) #cant be NULL
price = db.Column(db.Float, nullable=False)
isbn = db.Column(db.Integer)
#Adding books
def add_book(_name, _price, _isbn):
new_book=Book(name=_name, price=_price, isbn=_isbn)
db.session.add(new_book)
db.session.commit()
#get all books
def get_all_books():
return Book.query.all()
#get book by isbn
def get_book(_isbn):
return Book.query.filter_by(isbn=_isbn).first()
#output representation
def __repr__(self):
book_object={
'name': self.name,
'price': self.price,
'isbn': self.isbn
}
return json.dumps(book_object)
From terminal, call Python and import our BookModel and get all the books in it, now get any book’s ISBN from the list and pass it, we should get the corresponding book now, if you have followed the above steps.
TERMINAL:
#EXPECTED OUTPUT:
$ python
Python 3.6.8 (default)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from BookModel import *
>>> Book.get_all_books()
[{"name": "NewBook1", "price": 9.9, "isbn": 753357}, {"name": "NewBook2", "price": 1.3, "isbn": 432156}]
>>> Book.get_book(432156)
{"name": "NewBook2", "price": 1.3, "isbn": 432156}
>>> Book.get_book(753357)
{"name": "NewBook1", "price": 9.9, "isbn": 753357}
>>>
The delete_book method is similar to the get_book method that we wrote previously. Lets code it, define a method called delete_book which takes an ISBN we pass in. Run the Book.query method and filter_by isbn, same as we did above but here instead of running get the first result, we chain to the delete method it deletes all books that matches the filter. After we delete it, we need to save this changes by calling the db.session .commit that we had before.
Code Snippet_5.6- Updating our Data Model to Delete Entries from our Database
# 5.6 Updating our Data Model to Delete Entries from our Database
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import json
from settings import app #import app Object specifically
#creating a database Object
db = SQLAlchemy(app) #calling SQLAlchemy Constructor and passing it the app Object
#class Book inherit from db.Model
class Book(db.Model):
#define tablename
__tablename__ = 'books'
#define columns
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False) #cant be NULL
price = db.Column(db.Float, nullable=False)
isbn = db.Column(db.Integer)
#Adding books
def add_book(_name, _price, _isbn):
new_book=Book(name=_name, price=_price, isbn=_isbn)
db.session.add(new_book)
db.session.commit() #to save the changes
#get all books
def get_all_books():
return Book.query.all()
#get book by isbn
def get_book(_isbn):
return Book.query.filter_by(isbn=_isbn).first()
#delete book by isbn
def delete_book(_isbn):
Book.query.filter_by(isbn=_isbn).delete()
db.session.commit() #To save the changes
#output representation
def __repr__(self):
book_object={
'name': self.name,
'price': self.price,
'isbn': self.isbn
}
return json.dumps(book_object)
From terminal open up Python and then import our BookModel, now get all the books to see which one we want to delete. Call the Book.delete_book method and pass in any ISBN from the list. To check whether it has deleted, get all the books again.Now we must see the specific book removed.
TERMINAL:
#EXPECTED OUTPUT:
$ python
Python 3.6.8 (default)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from BookModel import *
>>> Book.get_all_books()
[{"name": "NewBook1", "price": 9.9, "isbn": 753357}, {"name": "NewBook2", "price": 1.3, "isbn": 432156}]
>>> Book.delete_book(432156)
>>> Book.get_all_books()
[{"name": "NewBook1", "price": 9.9, "isbn": 753357}]
>>>
Define update_book_price method which takes an ISBN of the book to update, and its price. The book_to_update method runs the same Book.query .filter_by with the passed in ISBN. To update, use dot notation to add a property that we want to update, then the value of the property and commit the change. To update the book’s name, define update_book_name and follow the same steps as above, instead of taking a price, it’ll take in a name, and it’ll update the name property here instead of the price property.
Code Snippet_5.7- Updating our Data Model to Update Table Entries:
# 5.7 Updating our Data Model to Update Table Entries
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import json
from settings import app #import app Object specifically
#creating a database Object
db = SQLAlchemy(app) #calling SQLAlchemy Constructor and passing it the app Object
#class Book inherit from db.Model
class Book(db.Model):
#define tablename
__tablename__ = 'books'
#define columns
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False) #cant be NULL
price = db.Column(db.Float, nullable=False)
isbn = db.Column(db.Integer)
#Adding books
def add_book(_name, _price, _isbn):
new_book=Book(name=_name, price=_price, isbn=_isbn)
db.session.add(new_book)
db.session.commit() #to save the changes
#get all books
def get_all_books():
return Book.query.all()
#get book by isbn
def get_book(_isbn):
return Book.query.filter_by(isbn=_isbn).first()
#delete book by isbn
def delete_book(_isbn):
Book.query.filter_by(isbn=_isbn).delete()
db.session.commit() #To save the changes
#to update a property (using Dot notation)
def update_book_name(_isbn, _name):
book_to_update = Book.query.filter_by(isbn=_isbn).first()
book_to_update.name = _name #using Dot notation,update property 'name'
db.session.commit() #save changes
def update_book_price(_isbn, _price):
book_to_update = Book.query.filter_by(isbn=_isbn).first()
book_to_update.price = _price
db.session.commit() #save changes
#output representation
def __repr__(self):
book_object={
'name': self.name,
'price': self.price,
'isbn': self.isbn
}
return json.dumps(book_object)
Open up terminal and test to make sure everything is working as expected. Open Python and import everything from our BookModel and get all the books in our collection. Now call the Book.update_book_price API, and the first parameter it takes is the ISBN. Change the price property to any value. Once the updation is done, do the Book.get_all_books, Now you can see the updated price, as expected. Check the update book name method, same as above.
TERMINAL:
#EXPECTED OUTPUT:
$ python
Python 3.6.8 (default)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from BookModel import *
>>> Book.get_all_books()
[{"name": "NewBook", "price": 9.9, "isbn": 753357}]
>>> Book.update_book_name(753357,"Book1")
>>> Book.get_all_books()
[{"name": "Book1", "price": 9.9, "isbn": 753357}]
>>> Book.update_book_price(753357,4.4)
>>> Book.get_all_books()
[{"name": "Book1", "price": 4.4, "isbn": 753357}]
>>>
Define a method replace_book, which takes an ISBN, a name and a price. We need to find the book to replace, define the method to run query to filter by ISBN. And now we need to update both properties instead of one through book_to_replace. price and book_to_replace.name which replaces the price and name passed in. Finally we need to run the db.session .commit, which will commit our changes to the database.
Code Snippet_5.8-Updating our Data Model to Replace Table Entries
# 5.8 Updating our Data Model to Replace Table Entries
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import json
from settings import app #import app Object specifically
#creating a database Object
db = SQLAlchemy(app) #calling SQLAlchemy Constructor and passing it the app Object
#class Book inherit from db.Model
class Book(db.Model):
#define tablename
__tablename__ = 'books'
#define columns
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False) #cant be NULL
price = db.Column(db.Float, nullable=False)
isbn = db.Column(db.Integer)
#Adding books
def add_book(_name, _price, _isbn):
new_book=Book(name=_name, price=_price, isbn=_isbn)
db.session.add(new_book)
db.session.commit() #to save the changes
#get all books
def get_all_books():
return Book.query.all()
#get book by isbn
def get_book(_isbn):
return Book.query.filter_by(isbn=_isbn).first()
#delete book by isbn
def delete_book(_isbn):
Book.query.filter_by(isbn=_isbn).delete()
db.session.commit() #To save the changes
#to update a property (using Dot notation)
def update_book_name(_isbn, _name):
book_to_update = Book.query.filter_by(isbn=_isbn).first()
book_to_update.name = _name #using Dot notation,update property 'name'
db.session.commit() #save changes
def update_book_price(_isbn, _price):
book_to_update = Book.query.filter_by(isbn=_isbn).first()
book_to_update.price = _price
db.session.commit() #save changes
#to replace Table Entries
def replace_book(_isbn, _name, _price):
book_to_replace = Book.query.filter_by(isbn=_isbn).first()
book_to_replace.name = _name
book_to_replace.price = _price
db.session.commit() #save changes
#output representation
def __repr__(self):
book_object={
'name': self.name,
'price': self.price,
'isbn': self.isbn
}
return json.dumps(book_object)
Once the coding is done, open a terminal and open up Python. From the BookModel import everything. Now get all the books, to replace a book call the Book.replace_book method, pass it an ISBN, a name and price parameters. After running this call the Book.get_all_books method, now we can see the ISBN is the same but all the parameters name and price are replaced.
TERMINAL:
#EXPECTED OUTPUT:
$ python
Python 3.6.8 (default)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from BookModel import *
>>> Book.get_all_books()
[{"name": "Book1", "price": 4.4, "isbn": 753357}]
>>> Book.replace_book(753357,"NewBook",10)
>>> Book.get_all_books()
[{"name": "NewBook", "price": 10.0, "isbn": 753357}]
>>>
We need to get a JSON representation of the data we are getting back from the above queries. Since SQLALCHEMY objects returned are not easy for us to interpret. Our book has a name field, price field and ISBN field which return self.name, self.price and self.isbn respectively. Change these queries, so instead of returning SQLAlchemy objects we’ll call our JSON method on it so that we can get JSON that we can use in our app.py file.
Our Book.query.all method returns book objects which is incompatible with our Flask application. Since, we need JSON back. For each book in our book query, we have to return the JSON representation of that. It’s going to loop through all the books that’s returned with the query, and then for each book we are going to call our json method, and this will be returned to us as a complete list in more human readable format
Code Snippet_5.9-Returning back JSON from our Data Model
# 5.9 Returning back JSON from our Data Model
#BookModel.py
#Snippet:
# __repr__ method is useful only in Terminal
#so we define a new method for json output
def json(self):
return{'name': self.name, 'price':self.price, 'isbn': self.isbn}
#get all books
def get_all_books():
return [Book.json(book) for book in Book.query.all()]
#Note we have returned the json format
We are going to use the BookModel that we created and migrate all of that into our app.py file. From the BookModel , and settings import everything.
Note 1: our settings.py has app object in it, so we can remove those code from app.py
Note 2: We don’t need the array of books to be defined in app.py, as we will be pulling them from our database. Rest of the edits are given in the code snippet accompanied with comments.
Code Snippet_5.10a- Updating our Flask App to use Our Book Model – app.py
# 5.10a- Updating our Flask App to use Our Book Model
# migrating all from BookModel and settings.py to app.py
#RSapp.py
from flask import Flask, jsonify,request,Response
from BookModel import *
from settings import *
import json #import Json
from settings import * #import all
#app = Flask(__name__) #removed since we share the Object from settings.py
"""
#since we will be pulling the books list from database,
#we dont need it defined here anymore
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
#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
Book.add_book(request_data['name'], request_data['price'], request_data['isbn'])
response = Response("", status=201, mimetype='application/json')
response.headers['Location'] = "/books" + str(request_data['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':Book.get_all_books()}) #from database
#GET/books/isbn
@app.route('/books/<int:isbn>')
def get_by_isbn(isbn):
return_value=Book.get_by_isbn(isbn)
return jsonify(return_value)
#PUT /books/5869
#{
# 'name': 'NewName',
# 'price': 456
#}
def valid_put_request_data(request_data):
if ("name" in request_data
and "price" in request_data):
return True
else:
return False
#PUT route
@app.route('/books/<int:isbn>', methods=['PUT'])
def replace_book(isbn):
request_data=request.get_json()
if(not valid_put_request_data(request_data)):
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
Book.replace_book(isbn, request_data['name'],request_data['price'])
response = Response("", status=204) #No content created
return response
# PATCH /books/isbn
#{
# 'name': 'UpdateNameAlone'
#}
# PATCH /books/isbn
#{
# 'price': 'UpdatePriceAlone'
#}
def valid_patch_request_data(request_data):
if ("name" in request_data
or "price" in request_data):
return True
else:
return False
@app.route('/books/<int:isbn>', methods=['PATCH'])
def update_book(isbn):
request_data = request.get_json() #we get the JSON data
if(not valid_patch_request_data(request_data)):
invalidBookObjectErrorMsg = {
"error": "Invalid book object passed in request",
"helpstring": "Data should be in following format{'name': 'bookname', 'price':15}"
}
response = Response (json.dumps(invalidBookObjectErrorMsg),staus=404,mimetype='application/json')
return response
if("name" in request_data):
Book.update_book_name(isbn, request_data['name'])
if("price" in request_data):
Book.update_book_price(isbn, request_data['price'])
response = Response("",status=204) #204=Success
response.headers['Location'] = "/books/" + str(isbn)
return response
#DELETE /books/isbn
@app.route('/books/<int:isbn>', methods=['DELETE'])
def delete_book(isbn):
if(Book.delete_book(isbn)):
response = Response("",status=204)
return response
invalidBookObjectErrorMsg = {
"error":"Unable to Delete,as ISBN provided doesnot found in the List"
}
response = Response(json.dumps(invalidBookObjectErrorMsg),status=404, mimetype='application/json') #Status404:Resource not found
return response
app.run(port=5000)
Code Snippet_5.10b- Updating our Flask App to use Our Book Model – BookModel.py
# 5.10b- Updating our Flask App to use Our Book Model
#BookModel.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import json
from settings import app #import app Object specifically
#creating a database Object
db = SQLAlchemy(app) #calling SQLAlchemy Constructor and passing it the app Object
#class Book inherit from db.Model
class Book(db.Model):
#define tablename
__tablename__ = 'books'
#define columns
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False) #cant be NULL
price = db.Column(db.Float, nullable=False)
isbn = db.Column(db.Integer)
# __repr__ method is useful only in Terminal
#so we define a new method for json output
def json(self):
return{'name': self.name, 'price':self.price, 'isbn': self.isbn}
#Adding books
def add_book(_name, _price, _isbn):
new_book=Book(name=_name, price=_price, isbn=_isbn)
db.session.add(new_book)
db.session.commit() #to save the changes
#get all books
def get_all_books():
return [Book.json(book) for book in Book.query.all()]
#get book by isbn
def get_book(_isbn):
return Book.json(Book.query.filter_by(isbn=_isbn).first())
#delete book by isbn
def delete_book(_isbn):
is_successful = Book.query.filter_by(isbn=_isbn).delete()
db.session.commit() #To save the changes
return bool(is_successful)
#to update a property (using Dot notation)
def update_book_name(_isbn, _name):
book_to_update = Book.query.filter_by(isbn=_isbn).first()
book_to_update.name = _name #using Dot notation,update property 'name'
db.session.commit() #save changes
def update_book_price(_isbn, _price):
book_to_update = Book.query.filter_by(isbn=_isbn).first()
book_to_update.price = _price
db.session.commit() #save changes
#to replace Table Entries
def replace_book(_isbn, _name, _price):
book_to_replace = Book.query.filter_by(isbn=_isbn).first()
book_to_replace.name = _name
book_to_replace.price = _price
db.session.commit()
#output representation
def __repr__(self):
book_object={
'name': self.name,
'price': self.price,
'isbn': self.isbn
}
return json.dumps(book_object)
Code Snippet_5.10c- Updating our Flask App to use Our Book Model – settings.py
#5.10c- Updating our Flask App to use Our Book Model
#settings.py
from flask import Flask
#Flask Constructor to create app Object
app = Flask(__name__) #this app Object is shared with RSapp.py file
#config to store SQLALCHEMY_DATABASE_URI,
#this will be the path where we'll store database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////home/RSflaskAPI/database.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# my project directory path: /home/RSflaskAPI/database.db
Keep our Server running, drag over Postman to test different routes and make sure that everything is working. Do a GET request, we can see the books in our book collection. To run the delete route, pass it ISBN for the book you want to delete. You will get a 204 NO CONTENT, which is the correct success message. To check send the GET request again and notice that the specific book removed from list. To check PATCH request for a specific ISBN, (set the correct content type headers, and for the body change the price of the book or the name)now send the PATCH request and get a 204 NO CONTENT, which means that was success. To check it make a GET request again and notice the book gets updated.
To make sure that these changes are persistent, restart the server and give a GET request, all the changes made should be there, as expected if everything works correctly.
TERMINAL:
From Postman fire GET, POST, PUT, PATCH and DELETE requests -http methods
$ python RSapp.py
* Serving Flask app "settings" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [] "GET /books HTTP/1.1" 200 - #notice the status codes
127.0.0.1 - - [] "POST /books HTTP/1.1" 201 -
127.0.0.1 - - [] "GET /books HTTP/1.1" 200 -
127.0.0.1 - - [] "PATCH /books/123321 HTTP/1.1" 204 -
127.0.0.1 - - [] "GET /books HTTP/1.1" 200 -
127.0.0.1 - - [] "PUT /books/753357 HTTP/1.1" 204 -
127.0.0.1 - - [] "GET /books HTTP/1.1" 200 -
127.0.0.1 - - [] "DELETE /books/123321 HTTP/1.1" 204 -
127.0.0.1 - - [] "GET /books HTTP/1.1" 200 -
^C # to stop
$
By this we reached the end of this module,here we migrated our book list from being static into actually using a SQL database. We built out a book model that connected to our database, and then we migrated our REST API endpoints to use our book model instead of being hard-coded. In the next module, we will add authentication to our API. Stay updated..
love,
<R.S>
Complete Sourcecode available in my Github repo: Link provided in the final module.
⭐⭐⭐⭐⭐
Rating: 5 out of 5.
RS-codes

–
Welcome back,
This is our fourth module of building our flask API. In the last module, we finished implementing the PUT and PATCH HTTP methods, which allowed clients to update books that were already created in the store. And in this module we’ll implement the DELETE method.
In this module, we will discuss the following:
4.DELETING DATA
4.1 Adding a DELETE route
4.2 Coding our DELETE route
4.3 Sending DELETE requests to our Flask App
4.4 Finishing up our DELETE route
By the end of this module, you will be able to do delete books already in our bookstore using DELETE requests, and a clear view of what status codes and responses we should be sending for various scenarios.
Lets start coding…
The very first thing we need to do is to add a new route. This route will point to the same endpoint as our book’s ISBN route that allowed a user to get a single book from our store. Within this method we need to pass in DELETE, so the route knows which method to be called on, and then we’ll define a function delete_book, and it will take in that ISBN, and for now we’ll put in a pass.
Code Snippet_4.1- Adding a DELETE route:
#DELETE /books/isbn
@app.route('/books/<int:isbn>', methods=['DELETE'])
def delete_book(isbn):
pass
This route should look very similar to the previous routes where we had clients either update a book in this route, add a book to this route, patch a book, replace a book, etc.,
Save and Run from CLI, to check the code is working without any error. We are yet to define the method.
Now we are going to allow our client to send a DELETE request to a book with a given ISBN number. In the DELETE request, all we need to do is go through all the books in our collection, find one that matches the ISBN number that was passed in, and once we find a match, then we remove it from our store. (even if there are duplicate ISBN numbers, they all will be deleted). Our client will send us a DELETE request, and for the body they actually don’t need to send us anything. Unlike the previous route which we built where we actually need to use this request.get_json method, in this route we don’t actually need to do that because it doesn’t matter what body our clients send us.
Code Snippet_4.2- Coding our DELETE route
#DELETE /books/isbn
@app.route('/books/<int:isbn>', methods=['DELETE'])
def delete_book(isbn):
for book in books:
if book["isbn"] == isbn:
return jsonify(book) #<==juz to check
This return book statement is just to check whether our route is set up correctly and our server is able to receive DELETE request. Now add the snippet ,save and run the Server up from the terminal and open Postman to fire request.
Terminal:
Keep the server up
Postman:
GET Request:
#Expected Output for the above GET request:
{
"Books": [
{
"isbn": 123456,
"name": "Book1",
"price": 15.5
},
{
"isbn": 789654,
"name": "Book2",
"price": 12.2
}
]
}
Fire a DELETE request from Postman:
DELETE req at http://127.0.0.1:5000/books/789654
Response Body:
#Response Body:
{
"isbn": 789654,
"name": "Book2",
"price": 12.2
}
When we do a DELETE request to a ISBN, we should get the actual JSON body for that book’s ISBN, which would give us the name, the price, and the ISBN of it. We can make sure our DELETE route is successfully connected, if we get back the book data of the ISBN we passed into the URL.
We define a counter here. This counter is used to index which book we want to delete from our book collection. We use the lists pop method, and this takes an integer, which is the index in the list for whatever book we want to remove. For all the books in our book collection, if we have a book ISBN that matches the ISBN passed in, it’ll pop, which means it’ll remove that index from the total collection of our list. Because we need to make this counter instead of always being hard-coded to 0 actually increment each time a book is iterated through in our total collection. Now for every book in our books collection, we will add one to this counter, so by the end this will reach the total size of the book collection. For now we are going to return an empty string and we will test to make sure that this whole delete books is working.
Code Snippet_4.3- Sending DELETE request to our Flask App
#DELETE /books/isbn
@app.route('/books/<int:isbn>', methods=['DELETE'])
def delete_book(isbn):
i = 0 #counter 'i' , used to index which book we want to delete
for book in books:
if book["isbn"] == isbn:
books.pop(i) #removes that indexed content from List
i += 1 #increment it each time a book is iterated thru
return "" #empty string
Save and open up the terminal and then restart our server, as we did earlier. From Postman, first we do a GET request to make sure we can get the books, and then we try to delete first book from our collection. When we do this DELETE method and then we do a GET, we should not see the book that we deleted, anymore.
Terminal:
Restart the server up
Postman:
First give a GET request, as we did in our previous discussions.
#Expected Output after GET request
{
"Books": [
{
"isbn": 123456,
"name": "Book1",
"price": 15.5
},
{
"isbn": 789654,
"name": "Book2",
"price": 12.2
}
]
}
Now give a DELETE request at http://127.0.0.1:5000/books/789654
Status 200 OK
Empty body #returns nothing, since empty string
We get a 200 OK with an empty body, but this doesn’t necessarily mean that this is working, to make sure that things are working as expected. We give GET request again.
Giving a GET request again, now we should not see the book that we deleted.
#Expected output after giving GET request ,after Deleting a entry through DELETE req
{
"Books": [
{
"isbn": 123456,
"name": "Book1",
"price": 15.5
}
]
}
As you can see, everything is working as expected. Now the next thing we need to do is send back the correct response headers and status codes to our client, since we haven’t explicitly set those yet.
To finish up our DELETE route we add status code and response Location header. If we reach a case in which a Book with the ISBN number that was provided was not found. So therefore, unable to delete. That’s an error, so we need to return this invalid book object error message, and then we do json.dump it. That way it becomes a JSON object. And then we return the response back to the client. And the status code should be a 404(resource not found) because the resource wasn’t found because the only way a book cannot be deleted is if the ISBN does not match what we have in our current collection. Then we put the mimetype as application/json.
Runnable Code Snippet_4.4- Finishing up our DELETE route
# 4.4 Finishing up our DELETE route
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)
#PUT /books/5869
#{
# 'name': 'NewName',
# 'price': 456
#}
#PUT route
@app.route('/books/<int:isbn>', methods=['PUT'])
def replace_book(isbn):
request_data=request.get_json()
if(not valid_put_request_data(request_data)):
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
new_book = {
'name' : request_data['name'],
'price' : request_data['price'],
'isbn' : isbn
}
i = 0
for book in books:
currentIsbn=book["isbn"]
if currentIsbn == isbn:
books[i] = new_book
i += 1
response = Response("", status=204) #No content created
return response
# PATCH /books/isbn
#{
# 'name': 'UpdateNameAlone'
#}
# PATCH /books/isbn
#{
# 'price': 'UpdatePriceAlone'
#}
#PATCH route
@app.route('/books/<int:isbn>', methods=['PATCH'])
def update_book(isbn):
request_data = request.get_json() #we get the JSON data
updated_book = {} #placeholder for properties to be updated
if("name" in request_data):
updated_book["name"] = request_data['name']
if("price" in request_data):
updated_book["price"] = request_data['price']
for book in books:
if book["isbn"] ==isbn:
book.update(updated_book)
response = Response("",status=204) #204=Success
response.headers['Location'] = "/books/" + str(isbn)
return response
#DELETE route
#DELETE /books/isbn
@app.route('/books/<int:isbn>', methods=['DELETE'])
def delete_book(isbn):
i = 0; #counter 'i' , used to index which book we want to delete
for book in books:
if book["isbn"] == isbn:
books.pop(i) #removes that indexed content from List
response = Response("",status=204) #204=Success
return response
i += 1 #increment it each time a book is iterated thru
invalidBookObjectErrorMsg = {
"error":"Unable to Delete,as ISBN provided doesnot found in the List"
}
response = Response(json.dumps(invalidBookObjectErrorMsg),status=404, mimetype='application/json') #Status404:Resource not found
return response
app.run(port=5000)
Terminal:
Restart the server up
Postman:
Fire a GET request at http://127.0.0.1:5000/books
#Expected Output for the above GET request:
{
"Books": [
{
"isbn": 123456,
"name": "Book1",
"price": 15.5
},
{
"isbn": 789654,
"name": "Book2",
"price": 12.2
}
]
}
Lets check, how well it handles certain condition, say by giving an invalid ISBN in the DELETE request.
Now fire DELETE req at http://127.0.0.1:5000/books/7854
Response Body:
#Expected Output:
#Response Body:
{
"error": "Unable to Delete,as ISBN provided doesnot found in the List"
}
#Status: 404 NOT FOUND
By this we come to the end of this module. In this module, we started allowing code to allow users to delete books already in our bookstore using PUT requests. We then discussed what status codes and responses we should be sending for various scenarios. In the next module, we migrate our books list from being a static list into actually being pooled in the SQL database.
For complete Sourcecode visit my Github repo: Link provided in the final module
⭐⭐⭐⭐
Rating: 4 out of 5.
RS-codes

Welcome back,
This is our third module of building our flask API. In our previous modules we built our first flask application using Python, developed our route decorator for GET method and did POST request with a clean Sanitized Data which returns a meaningful Status Code and Response.
In this module, we will discuss the following:
3.UPDATING DATA
3.1 Adding a PUT route
3.2 Getting the Client’s request
3.3 Sending PUT request to Flask
3.4 Finishing up the PUT route
3.5 Adding a PATCH route
3.6 Defining our PATCH route
3.7 Finishing up our PATCH route
By the end of this module, you will be able to do PUT and PATCH requests to update the Data.
Lets get inside..
We’ll start adding code to allow users to update data.
To do this, we need to add a route. This will be the PUT route. This route will point to the same endpoint, as our previous one. We will define the method that should run whenever a client puts to our route.
# Adding a PUT route
#PUT route
@app.route(‘/books/<int:isbn>’, methods=[‘PUT’])
def replace_book(isbn):
pass #we will define it later
Code Snippet_3.1- Adding a PUT route:
# 3.1 Adding a PUT route
#PUT route
@app.route('/books/<int:isbn>', methods=['PUT'])
def replace_book(isbn):
pass #we will define it later
We defined the method as replace_book(), that takes the ISBN that was passed, and we put this as a pass for now and we’ll define it shortly, and for now we Run and check the added route is working.
Client needs to send all the details of the book they want to add in the request body as an object.
Request body should be like this:
{
‘name’: ‘NewName’,
‘price’: 456
}
Note:
Incase the Client wants to update a single field, we can use PATCH request(will discuss in detail, shortly)
i.e., for partial data update, we use a different HTTP method called PATCH method but for PUT request, we provide the full information (all data as object) needed by the Resource.
#PUT route
@app.route(‘/books/<int:isbn>’, methods=[‘PUT’])
def replace_book(isbn):
return jsonify(request.get_json()) #just checking whether we can get back the Data
As want to verify that we can indeed get the request body that our client sends to us by displaying it back to them that this is defined, we need to return this and then we need to convert this using jsonify, and that will set the correct headers back as application/json to the client.
Code Snippet_3.2- Getting the Client’s Request body:
#3.2-Getting the Client's Request body
#PUT route
@app.route('/books/<int:isbn>', methods=['PUT'])
def replace_book(isbn):
return jsonify(request.get_json())
Open up Postman and keep our server running. At firstwe do a GET request to our books. On Postman by pressing the plus icon, you can create a new request to use PUT. And for the body, this is an important point when using Postman, when you’re sending JSON, you want to put the body as raw. Enter the body, as shown in below and add JSON .
{
'name': 'NewName',
'price': 456
}
CLI command and Output:
$ python app.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Postman :
Step1:
Fire a GET request, view books and take an isbn no from the list.
Step2:
Add new Request(PUT) at URL http://127.0.0.1:5000/books/123456(isbn)
Header->ContentType- application/JSON
Body-> raw->application/JSON
Request body:
{
“name”: “Book1”,
“price”: 15.5
}
After send this PUT request, what we should expect to see is the body that we had sent back to us, and a 200 status OK, if we drag the Response window up, we must see the body same as the Request body we sent and status: 200 status OK and the content type of application/json.
Step3:
Press Send button
View the Response window at the bottom
ResponseBody:
{
“name”: “Book1”,
“price”: 15.5
}
Ensure Response body is same as the request body(application/JSON).
Runnable Code Snippet_3.4- Finishing up the PUT route:
# 3.4 Finishing up the PUT route
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')
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)
#PUT /books/5869
#{
# 'name': 'NewName',
# 'price': 456
#}
"""
to update a single field ,we use PATCH request(will discuss later)
"""
#PUT route
@app.route('/books/<int:isbn>', methods=['PUT'])
def replace_book(isbn):
request_data=request.get_json()
new_book = {
'name' : request_data['name'],
'price' : request_data['price'],
'isbn' : isbn
}
i = 0;
for book in books:
currentIsbn=book["isbn"] #if book["isbn"]==isbn:
if currentIsbn == isbn:
books[i] = new_book
i += 1
response = Response("", status=204) #No content created
return response
app.run(port=5000)
CLI command and Output:
$ python RSapp.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Postman:
Step1:
fire a GET request at http://127.0.0.1:5000/books
Request Body:
{
"Books": [
{
"isbn": 123456,
"name": "Book1",
"price": 15.5
},
{
"isbn": 789654,
"name": "Book2",
"price": 12.2
}
]
}
Step2:
fire a PUT request URL:http://127.0.0.1:5000/books/123456
Ensure you have enabled the type as follows: raw-application/JSON | Header-ContentType application/JSON
PUT requestbody:
{
"name" : "UpdatedBook",
"price" : 3.5
}
press SEND button
Step3:
Check the Response window
Status: 204 NO CONTENT
ResponseBody:
empty
Step4:
GET request again at http://127.0.0.1:5000/books
{
"Books": [
{
"isbn": 123456, <===Updated fields(got from URL!!)
"name": "UpdatedBook", <===Updated fields
"price": 3.5 <===Updated fields
},
{
"isbn": 789654,
"name": "Book2",
"price": 12.2
}
]
}
We can see the updated list from the above window, as expected.
Before getting into PATCH route, we clear up one more thing, that is adding a Data Sanitizer for our previous PUT route.
Code Snippet_3.5- Data sanitizing in ‘PUT’ method:
#add this snippet to sanitize the data in PUT method
#PUT route
@app.route('/books/<int:isbn>', methods=['PUT'])
def replace_book(isbn):
request_data=request.get_json()
if(not valid_put_request_data(request_data)): <===this snippet
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
Adding Patch Route:
In PATCH method Request body can pass only specific field , that we want to be updated. For example, we can either update the Bookname or the price whichever we want to be updated.
# PATCH /books/isbn
{
‘name’: ‘UpdateNameAlone’
}
or price alone as follows:
# PATCH /books/isbn
{
‘price’: ‘123.45’
}
@app.route('/books/<int:isbn>', methods=['PATCH'])
def update_book(isbn):
pass #we will define it later
Code Snippet_3.6- Defining a PATCH route:
# 3.6 Defining our PATCH route
# PATCH /books/isbn
#{
# 'name': 'UpdateNameAlone'
#}
@app.route('/books/<int:isbn>', methods=['PATCH'])
def update_book(isbn):
request_data = request.get_json() #we get the JSON data
updated_book = {} #placeholder for properties to be updated
if("name" in request_data):
updated_book["name"] = request_data['name']
for book in books:
if book["isbn"] ==isbn:
book.update(updated_book)
response = Response("",status=204)
response.headers['Location'] = "/books/" + str(isbn)
return response
When we’re coding up this route, we should go the way same as we did before.
If we want to update price field
# PATCH /books/isbn
{
‘price’: ‘123.45’ #UpdatePriceAlone
}
#Snippet:
if("price" in request_data):
updated_book["price"] = request_data['price']
Runnable Code Snippet_3.7-Finishing up our PATCH request :
#app.py
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')
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)
#PUT /books/5869
#{
# 'name': 'NewName',
# 'price': 456
#}
#PUT route
@app.route('/books/<int:isbn>', methods=['PUT'])
def replace_book(isbn):
request_data=request.get_json()
if(not valid_put_request_data(request_data)):
invalidBookObjectErrorMsg = {
"error" : "Invalid book object in request",
"helpString" : "Pls pass Data in following format {'name':'bookname', 'price':5.9, 'isbn':852569}"
}
response = Response(json.dumps(invalidBookObjectErrorMsg),status=400,mimetype='application/json')
return response
new_book = {
'name' : request_data['name'],
'price' : request_data['price'],
'isbn' : isbn
}
i = 0;
for book in books:
currentIsbn=book["isbn"]
if currentIsbn == isbn:
books[i] = new_book
i += 1
response = Response("", status=204) #No content created
return response
# PATCH /books/isbn
#{
# 'name': 'UpdateNameAlone'
#}
# PATCH /books/isbn
#{
# 'price': 'UpdatePriceAlone'
#}
@app.route('/books/<int:isbn>', methods=['PATCH'])
def update_book(isbn):
request_data = request.get_json() #we get the JSON data
updated_book = {} #placeholder for properties to be updated
if("name" in request_data):
updated_book["name"] = request_data['name']
if("price" in request_data):
updated_book["price"] = request_data['price']
for book in books:
if book["isbn"] ==isbn:
book.update(updated_book)
response = Response("",status=204)
response.headers['Location'] = "/books/" + str(isbn)
return response
app.run(port=5000)
CLI Command and Output:
$ python RSapp.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Postman:
fire a GET request at http://127.0.0.1:5000/books
GET-Requestbody:
#GET-Requestbody
{
"Books": [
{
"isbn": 123456,
"name": "Book1",
"price": 15.5
},
{
"isbn": 789654,
"name": "Book2",
"price": 12.2
}
]
}
fire a PATCH request “name” field alone at http://127.0.0.1:5000/books/123456
PATCH RequestBody (for name field alone):
#PATCH-RequestBody:
{
"name" : "UpdateNameAlone"
}
PATCH-ResponseBody:
{
"Books": [
{
"isbn": 123456,
"name": "UpdateNameAlone",
"price": 15.5
},
{
"isbn": 789654,
"name": "Book2",
"price": 12.2
}
]
}
#PATCH request “price” field alone at http://127.0.0.1:5000/books/123456
PATCH-RequestBody(for price field alone):
{
"price" : 6.6
}
ResponseBody:
{
"Books": [
{
"isbn": 123456,
"name": "Book1",
"price": 6.6 <==updated field
},
{
"isbn": 789654,
"name": "Book2",
"price": 12.2
}
]
}
#Status : 204 NO CONTENT (Success but return nothing)
If we hit the Send button , then we also get a 204 no content, and look at the headers, and we can see the location header of the updated resource.
So,this is the end of this module and in this module we started adding code to allow users to replace books already in our bookstore using PUT requests. We then handled a case where a client only wants to update a certain attribute and not have them send all book properties back to us using a PATCH request. In the next module, we will implement the HTTP DELETE method, which will allow clients to delete a book.
For complete Sourcecode visit my Github repo: Link provided in the final module
⭐⭐⭐
Rating: 3 out of 5.
RS-codes

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..
#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.
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)
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)
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)
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$
#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
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)
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)
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
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

Hi all,
This write-up is aimed to share the knowledge i gained. I recently tried building a Flask API with python, tried playing around few HTTP methods available, created database and added Authentication to the API created.
In this blog I tried to explain them in simple words, so that somewhere a newbie looking for a resource (just like i did recently) might get benefited someday..
I divided the task into six smaller modules for better understanding and easy approach. In this blog we are going to learn through Coding it. I assume the reader has a basic Programming skill, basic Python knowledge and few Terminal Commands.
Dont worry, if you are new to python/flask, I added adequate comments for the snippets we code here. So, just start coding parallely, you will get it in due course, as we proceed.
So, lets start Learning by Doing…!!!
After finishing the complete series of this blog, you would be able to build and integrate the following things on your own :
1.GETTING DATA
1.1 Building our First Flask Application
1.2 Returning List of Books from our API
1.3 Returning a single Book from our API
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
3.UPDATING DATA
3.1 Adding a PUT route
3.2 Getting the Client’s request
3.3 Sending PUT request to Flask
3.4 Finishing up the PUT route
3.5 Adding a PATCH route
3.6 Defining our PATCH route
3.7 Finishing up our PATCH route
4.DELETING DATA
4.1 Adding a DELETE route
4.2 Coding our DELETE route
4.3 Sending DELETE requests to our Flask App
4.4 Finishing up our DELETE route
5.STORING DATA IN SQL DB
5.1 Configuring a Flask SQLAlchemy Database
5.2 Defining Tables and Columns in Flask SQLAlchemy
5.3 Creating the Database
5.4 Defining a Data model with Flask SQLAlchemy
5.5 Updating our Data Model to Query our Database
5.6 Updating our Data Model to Delete Entries from our Database
5.7 Updating our Data Model to Update Table Entries
5.8 Updating our Data Model to Replace Table Entries
5.9 Returning back JSON from our Data Model
5.10 Updating our Flask App to use Our Book Model
5.11 Verifying the Book Model integration
6.ADDING AUTHENTICATION TO OUR API
6.1 Creating a JWT Tokens
6.2 Adding Authentication for our GET route
6.3 Creating a Database for Users who are allowed API access
6.4 Creating Users ,who are allowed API access
6.5 Having User Login to get a Token
6.6 Creating Decorators to Add API Authentication
6.7 Adding Authentication for all our routes using Decorators
In our first read lets focus on the first module – GETTING DATA. We simplify the concepts by taking an example ‘Book List’. By the end of this first module, you would be able to do the following on your own:
Lets get started…!!! 🙂
Prerequisite:
Create a python file and open it in an editor of your choice. I used Sublime Text Editor.
The Terminal commands to create and open the file in editor (in my case, SublimeText) are :
$ touch RSapp.py
$ subl RSapp.py
we have now opened a python file ( RSapp.py ) to build our first flask application.
First thing first, import Flask class into our python file
from flask import Flask #importing the Flask Class
Create an instance of this Flask class. 1st argument is the name of the Application’s module or Package. It is needed, so that Flask knows where to look for templates, static files and packages, when needed.
For eg., if the python interpreter is running that module, the source file, as the main program,then it sets the __name__ variable equal to value of main i.e., __name__=”main”, if it is imported from another module, the name will be set to the module’s name i.e., __name__=”modulesName”
app = Flask(__name__)
print(__name__) #prints the name when we start the server,this is just for our ref.,
Route Decorator:
Creating our 1st route with a route decorator. Route decorator binds a function to a URL. In simple words when someone hits the path specified by this route function, then the method defined below the route decorator will be called.
@app.route(‘/’) #route decorator
def hello_world(): #function that returns hello world
return ‘Hello world .!’ #when route directory is hit, it returns hello world
app.run(port=5000) #running that flask instance at localhost port 5000
Runnable Code Snippet_1.1 – Building our First Flask Application:
#RSapp.py
from flask import Flask
app = Flask(__name__)
print(__name__)
@app.route('/')
def hello_world():
return 'Hello world .!'
app.run(port=5000)
Now, save and run the app from Terminal (Note: cd to your app’s file location)
CLI Output:
rs@rs-pc:~/RSflaskAPI$ python RSapp.py #command to run our app
__main__ //output of print(__name__) statement
* Serving Flask app “RSapp”
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
On hitting the URL , we get Hello world .! message displayed in a default browser window localhost.
Note: By hitting CTRL+C from the terminal we can stop our app Server , ^C indicates the server has been stopped successfully
Import jsonify library along with Flask class, so that we can return a JSON object.
#importing Flask Class and jsonify library
from flask import Flask,jsonify
#creating an instance of Flask class
app = Flask(__name__)
#Creating books List[], with 2 Dictionaries{}
books=[ #List
{ #1st Dictionary
‘name’ : ‘Book1’, #fields- name , price & isbn
‘price’ : 20,
‘isbn’ : 123
},
{ #2nd Dictionary
‘name’ : ‘Book2’,
‘price’ : 10,
‘isbn’ : 456
}
]
#route decorator
whenever someone goes to route,in this case we make it as /books
@app.route(‘/books’) #by default GET request
def get_books(): #method get_books() will be called and
#to return a pythonList
return {‘books’: books} #returns the LIST ,but we want a JSON object
so we return the jsonified books list,this is the reason we imported jsonify library.
return jsonify({‘books’:books})
Note: if someone goes to route. By default, it is set to GET request
i.e., GET /books
Note: if we want to override that, we can add a different method as follows.,
@app.route(‘/books’, methods=[‘POST’]), we will this discuss later in our upcoming module.
Runnable Snippet_1.2: Returning List of Books from our API :
#RSapp.py
from flask import Flask,jsonify
app = Flask(__name__)
books=[
{
'name' : 'Book1',
'price' : 20,
'isbn' : 123
},
{
'name' : 'Book2',
'price' : 10,
'isbn' : 456
}
]
@app.route('/books')
def get_books():
return jsonify({'books':books})
app.run(port=5000)
Now, save and run the app from Terminal
CLI Output:
rs@rs-pc:~/RSflaskAPI$ python RSapp.py #command to run our app
* Serving Flask app “RSapp”
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
On hitting the URL http://127.0.0.1:5000/books , we get the json data of the books List displayed in our route directory /books localhost.
Here we import request library along with Flask and jsonify.
#import request library, along with others as earlier
from flask import Flask,jsonify,request
Create an instance of Flask class, create books List[], with 2 Dictionaries{} and create route decorators , as we did in our previous program.
app = Flask(__name__)
books=[
{
‘name’ : ‘Book1’,
‘price’ : 20,
‘isbn’ : 123
},
{
‘name’: ‘Book2’,
‘price’: 10,
‘isbn’: 456
}
]
#GET /store
@app.route(‘/books’)
def get_books():
return jsonify({‘books’:books})
To get all the Data of a Single Book in our Store, through ISBN:
@app.route(‘/books/<int:isbn>’) #GET request by passing isbn_no Note: typecasted to int
def get_books_by_isbn(isbn): #ISBN that’s passed in URL will be passed into ftn as arg(stored in variable isbn)
return_value={} #defined return_value as empty dictionary object
for book in books: #in our List
if book[“isbn”] == isbn: #if matches the isbn passed(var in the rightside)
return_value = { #then adds the properties defined in List into the return_value
‘name’: book[“name”],
‘price’: book[“price”]
}
return jsonify(return_value) #return_value is a Dictionary,but we need a JSON object
app.run(port=5000)
Runnable Snippet_1.3: Returning a single Book from our API – (allows a Client on the API to get all the Data of a Single Book in our Store, through ISBN):
#RSapp.py-Returning a single Book from our API
from flask import Flask,jsonify
app = Flask(__name__)
books=[
{
'name' : 'Book1',
'price' : 20,
'isbn' : 123
},
{
'name' : 'Book2',
'price' : 10,
'isbn' : 456
}
]
#GET /books
@app.route('/books')
def get_books():
return jsonify({'books':books})
#GET /books/ISBN_NUMBER - ”GET request by passing isbn_no”
#GET /books/123
@app.route('/books/<int:isbn>')
def get_books_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)
Now, save and run the app from Terminal
CLI Output:
rs@rs-pc:~/RSflaskAPI$ python RSapp.py #command to run our app
* Serving Flask app “RSapp”
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
On hitting the URL http://127.0.0.1:5000/books , we get the json data of the books List displayed in our route directory /books localhost.
From the List take an isbn (from our example isbn:456 is of Book2) and give it in URL http://127.0.0.1:5000/books/456
we get the json data of the book (Book2 from our example)
We have successfully completed the first module (Getting Data), please do read and try your hands and let me know, if there is any error.
We will play with some HTTP methods available in the upcoming modules. Visit them, as well and Stay updated.
Corrections and Suggestions are highly appreciated. Please leave a reply, in case you find any..
*CLI – Command Line Interface
For Complete Sourcecode visit my Github repo: Link provided in the final module.
⭐⭐⭐⭐
Rating: 4 out of 5.
RS-codes