Handling Request Data w/Flask

This article is about the ways that I have recently learned to handle the data from client requests to the server. While there are other ways to do so than are mentioned here, in my opinion these ways seem to be the most efficient and easiest to learn for an up and coming engineer.

When handling client requests with Flask, there are 3 main ways the data for the request can be presented to the server. All 3 require you to import 'flask' and the 'request' module from flask by:

from flask import Flask, request

The first is with params.

Params pass information to the server by embedding data in the url. The data follows the domain and path the user is trying to connect with and is separated by various symbols. For example using Postman I can send the following request through the url to the server:

https://127.0.0.1:5555/example/login?username=brian&password=p_word

I sent login data to localhost at port 5555 via the path '/example/login' with the params of 'username' that equals 'brian' and 'password' that equals 'p_word'. I can gain access to the params on the server side using the following:

#!/usr/bin/env python3

from flask import Flask, jsonify, request, make_response
from flask_migrate import Migrate
from flask_restful import Api, Resource

from models import db, Example

import json

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///example.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.json.compact = False

migrate = Migrate(app, db)
db.init_app(app)

api = Api(app)

class Login(Resource):
    def get(self):
        try:
            args = (request.args).to_dict()
            username = args.get('username')
            password = args.get('password')
            return make_response(args, 200)
        except (ValueError, AttributeError, TypeError) as e:
            return make_response(
                {"error": [str(e)]},
                400
            )

api.add_resource(Login, '/example/login')

'request.args' will grab the params from the url and return the following:

ImmutableMultiDict([('username', 'brian'), ('password', 'p_word')])

Then the method '.to_dict()' will convert the 'ImmutableMultiDict' to a dictionary with key/value pairs that you can access with square bracket notation or the '.get()' method that I used. I prefer the '.get()' because it will not crash the program if the key does not exist. Instead it will just return 'None'. I can then do whatever I want with the values of the params like compare them to values in the database for authentication.

The next way of processing data from an incoming request is when the data comes with a form submission. It is very similar to the first way you use '.form' instead of '.args':

class Login(Resource):
    def get(self):
        try:
            form_data = (request.form).to_dict()
            username = form_data.get('username')
            password = form_data.get('password')
            return make_response(form_data, 200)
        except (ValueError, AttributeError, TypeError) as e:
            return make_response(
                {"error": [str(e)]},
                400
            )

'request.form' will return the same data structure as seen before, needing the '.to_dict()' method to turn it into a usable data type:

ImmutableMultiDict([('username', 'brian'), ('password', 'p_word')])

The last way is through the body data of the request that is accessible with the 'request.data' method instead of the '.args'. This is where request handling, for me, takes a bit of a detour. The data is returned in byte string format before needing to be converted:

b'{\n  "username": "brian",\n  "password": "p_word",\n}'

It is recommended to use the '.get_json()' method to convert the byte string for a number of reasons. Like it automatically converts your data to a JSON response object and it automatically sets the correct response headers and content type for JSON responses. But how does it handle something like an empty string or backspaced character?

In these edge cases, it converts the request data into a response object. Which in turn is not usable for database comparison unless you use other methods to continue to convert the data into a usable data-type. So far, this will convert one of the edge case to a response object to byte string, but you need further methods to get the data to a usable type:

body_json = jsonify(request.get_json(force=True)).data

This seems to be getting a bit long.

It is far easier to use a method from the 'json' module imported earlier. A method called 'json.loads()'. This method will attempt to convert the request data to json, and if unsuccessful, will return the data itself. I passed the argument 'strict=False' so the method could handle special characters like backspaced characters. Then I made sure the request data was a dictionary because other data types do not have the '.get()' method:

class Login(Resource):
    def get(self):
        try:
            body_data = json.loads(request.data, strict=False)
            if type(body_data) in (dict,):
                username = body_data.get('username')
                password = body_data.get('password')
                response = body_data
            else:
                response = body_data
            return make_response(response, 200)
        except (ValueError, AttributeError, TypeError) as e:
            return make_response(
                {"error": [str(e)]},
                400
            )

This last method will not convert the data to byte string and therefore will bypass any further methods of conversion, making your request data usable for your application.

The three ways of processing request data that I have shown here should get you through most of the average data requests a beginning engineer should be expected to encounter. I hope this blog has been educational. Until next time!