Tutorial

How To Process Incoming Request Data in Flask

PythonPython Frameworks

Introduction

Web applications frequently require processing incoming request data from users. This payload can be in the shape of query strings, form data, and JSON objects. Flask, like any other web framework, allows you to access the request data.

In this tutorial, you will build a Flask application with three routes that accept either query strings, form data, or JSON objects.

Prerequisites

To complete this tutorial, you will need:

  • This project will require Python installed in a local environment.
  • This project will use Pipenv, a production-ready tool that aims to bring the best of all packaging worlds to the Python world. It harnesses Pipfile, pip, and virtualenv into one single command.
  • Downloading and installing a tool like Postman will be required for testing API endpoints.

This tutorial was verified with Pipenv v2020.11.15, Python v3.9.0, and Flask v1.1.2.

Setting Up the Project

To demonstrate the different ways of using requests, you will need to create a Flask app. Even though the example app uses a simplified structure for the view functions and routes, what you learn in this tutorial can be applied to any method of organizing your views like class-based views, blueprints, or an extension like Flask-Via.

First, you will need to create a project directory. Open your terminal and run the following command:

  • mkdir flask_request_example

Then, navigate to the new directory:

  • cd flask_request_example

Next, install Flask. Open your terminal and run the following command:

  • pipenv install Flask

The pipenv command will create a virtualenv for this project, a Pipfile, install flask, and a Pipfile.lock.

To activate the project’s virtualenv, run the following command:

  • pipenv shell

To access the incoming data in Flask, you have to use the request object. The request object holds all incoming data from the request, which includes the mimetype, referrer, IP address, raw data, HTTP method, and headers, among other things.

Although all the information the request object holds can be useful, for the purposes of this article, you will focus on the data that is normally directly supplied by the caller of the endpoint.

To gain access to the request object in Flask, you will need to import it from the Flask library:

from flask import request

You then have the ability to use it in any of your view functions.

Use your code editor to create an app.py file. Import Flask and the request object. And also establish routes for query-example, form-example, and json-example:

app.py
# import main Flask class and request object
from flask import Flask, request

# create the Flask app
app = Flask(__name__)

@app.route('/query-example')
def query_example():
    return 'Query String Example'

@app.route('/form-example')
def form_example():
    return 'Form Data Example'

@app.route('/json-example')
def json_example():
    return 'JSON Object Example'

if __name__ == '__main__':
    # run app in debug mode on port 5000
    app.run(debug=True, port=5000)

Next, open your terminal and start the app with the following command:

  • python app.py

The app will start on port 5000, so you can view each route in your browser with the following links:

http://127.0.0.1:5000/query-example (or localhost:5000/query-example)
http://127.0.0.1:5000/form-example (or localhost:5000/form-example)
http://127.0.0.1:5000/json-example (or localhost:5000/json-example)

The code establishes three routes and visiting each route will display the messages of "Query String Example", "Form Data Example", and "JSON Object Example" respectively.

Using Query Arguments

URL arguments that you add to a query string are a common way to pass data to a web app. While browsing the web, you will have likely encountered a query string before.

A query string resembles the following:

example.com?arg1=value1&arg2=value2

The query string begins after the question mark (?) character:

example.com?arg1=value1&arg2=value2

And has key-value pairs separated by an ampersand (&) character:

example.com?arg1=value1&arg2=value2

For each pair, the key is followed by an equals sign (=) character and then the value.

arg1 : value1
arg2 : value2

Query strings are useful for passing data that does not require the user to take action. You could generate a query string somewhere in your app and append it to a URL so when a user makes a request, the data is automatically passed for them. A query string can also be generated by forms that have GET as the method.

Let’s add a query string to the query-example route. In this hypothetical example, you will provide the name of a programming language that will be displayed on the screen. Create a key of "language" and a value of "Python":

http://127.0.0.1:5000/query-example?language=Python

If you run the app and navigate to that URL, you will see that it still displays a message of "Query String Example".

You will need to program the part that handles the query arguments. This code will read in the language key by using either request.args.get('language') or request.args['language'].

By calling request.args.get('language'), the application will continue to run if the language key doesn’t exist in the URL. In that case, the result of the method will be None.

By calling request.args['language'], the app will return a 400 error if the language key doesn’t exist in the URL.

When dealing with query strings, it is recommended to use request.args.get() to prevent the app from failing.

Let’s read the language key and display it as output.

Modify the query-example route in app.py with the following code:

app.py
@app.route('/query-example')
def query_example():
    # if key doesn't exist, returns None
    language = request.args.get('language')

    return '''<h1>The language value is: {}</h1>'''.format(language)

Then, run the app and navigate to the URL:

http://127.0.0.1:5000/query-example?language=Python

The browser should display the following message:

Output
The language value is: Python

The argument from the URL gets assigned to the language variable and then gets returned to the browser.

To add more query string parameters, you can append ampersands and the new key-value pairs to the end of the URL. Create a key of "framework" and a value of "Flask":

http://127.0.0.1:5000/query-example?language=Python&framework=Flask

And if you want more, continue adding ampersands and key-value pairs. Create a key of "website" and a value of "DigitalOcean":

http://127.0.0.1:5000/query-example?language=Python&framework=Flask&website=DigitalOcean

To gain access to those values, you will still use either request.args.get() or request.args[]. Let’s use both to demonstrate what happens when there is a missing key. Modify the query_example route to assign the value of the results to variables and then display them:

@app.route('/query-example')
def query_example():
    # if key doesn't exist, returns None
    language = request.args.get('language')

    # if key doesn't exist, returns a 400, bad request error
    framework = request.args['framework']

    # if key doesn't exist, returns None
    website = request.args.get('website')

    return '''
              <h1>The language value is: {}</h1>
              <h1>The framework value is: {}</h1>
              <h1>The website value is: {}'''.format(language, framework, website)

Then, run the app and navigate to the URL:

http://127.0.0.1:5000/query-example?language=Python&framework=Flask&website=DigitalOcean

The browser should display the following message:

Output
The language value is: Python The framework value is: Flask The website value is: DigitalOcean

Remove the language key from the URL:

http://127.0.0.1:5000/query-example?framework=Flask&website=DigitalOcean

The browser should display the following message with None when a value is not provided for language:

Output
The language value is: None The framework value is: Flask The website value is: DigitalOcean

Remove the framework key from the URL:

http://127.0.0.1:5000/query-example?language=Python&website=DigitalOcean

The browser should encounter an error because it is expecting a value for framework:

Output
werkzeug.exceptions.BadRequestKeyError werkzeug.exceptions.BadRequestKeyError: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand. KeyError: 'framework'

Now you understand handling query strings. Let’s continue to the next type of incoming data.

Using Form Data

Form data comes from a form that has been sent as a POST request to a route. So instead of seeing the data in the URL (except for cases when the form is submitted with a GET request), the form data will be passed to the app behind the scenes. Even though you cannot easily see the form data that gets passed, your app can still read it.

To demonstrate this, modify the form-example route in app.py to accept both GET and POST requests and returns a form:

app.py
# allow both GET and POST requests
@app.route('/form-example', methods=['GET', 'POST'])
def form_example():
    return '''
              <form method="POST">
                  <div><label>Language: <input type="text" name="language"></label></div>
                  <div><label>Framework: <input type="text" name="framework"></label></div>
                  <input type="submit" value="Submit">
              </form>'''

Then, run the app and navigate to the URL:

http://127.0.0.1:5000/form-example

The browser should display a form with two input fields - one for language and one for framework - and a submit button.

The most important thing to know about this form is that it performs a POST request to the same route that generated the form. The keys that will be read in the app all come from the name attributes on our form inputs. In this case, language and framework are the names of the inputs, so you will have access to those in the app.

Inside the view function, you will need to check if the request method is GET or POST. If it is a GET request, you can display the form. Otherwise, if it is a POST request, then you will want to process the incoming data.

Modify the form-example route in app.py with the following code:

app.py
# allow both GET and POST requests
@app.route('/form-example', methods=['GET', 'POST'])
def form_example():
    # handle the POST request
    if request.method == 'POST':
        language = request.form.get('language')
        framework = request.form.get('framework')
        return '''
                  <h1>The language value is: {}</h1>
                  <h1>The framework value is: {}</h1>'''.format(language, framework)

    # otherwise handle the GET request
    return '''
           <form method="POST">
               <div><label>Language: <input type="text" name="language"></label></div>
               <div><label>Framework: <input type="text" name="framework"></label></div>
               <input type="submit" value="Submit">
           </form>'''

Then, run the app and navigate to the URL:

http://127.0.0.1:5000/form-example

Fill out the language field with value of Python and the framework field with the value of Flask. Then, press Submit.

The browser should display the following message:

Output
The language value is: Python The framework value is: Flask

Now you understand handling form data. Let’s continue to the next type of incoming data.

Using JSON Data

JSON data is normally constructed by a process that calls the route.

An example JSON object looks like this:

{
    "language" : "Python",
    "framework" : "Flask",
    "website" : "Scotch",
    "version_info" : {
        "python" : "3.9.0",
        "flask" : "1.1.2"
    },
    "examples" : ["query", "form", "json"],
    "boolean_test" : true
}

This structure can allow for much more complicated data to be passed as opposed to query strings and form data. In the example, you see nested JSON objects and an array of items. Flask can handle this format of data.

Modify the form-example route in app.py to accept POST requests and ignore other requests like GET:

app.py
@app.route('/json-example', methods=['POST'])
def json_example():
    return 'JSON Object Example'

Unlike the web browser used for query strings and form data, for the purposes of this article, to send a JSON object, you will use Postman to send custom requests to URLs.

Note: If you need assistance navigating the Postman interface for requests, consult the official documentation.

In Postman, add the URL and change the type to POST. On the body tab, change to raw and select JSON from the drop-down.

These settings are required so Postman can send JSON data properly, and so your Flask app will understand that it is receiving JSON:

POST http://127.0.0.1:5000/json-example
Body
raw JSON

Next, copy the earlier JSON example into the text input.

Send the request, and you should get "JSON Object Example" as the response. That is fairly anti-climatic but is to be expected because the code for handling the JSON data response has yet to be written.

To read the data, you must understand how Flask translates JSON data into Python data structures:

  • Anything that is an object gets converted to a Python dict. {"key" : "value"} in JSON corresponds to somedict['key'], which returns a value in Python.
  • An array in JSON gets converted to a list in Python. Since the syntax is the same, here’s an example list: [1,2,3,4,5]
  • The values inside of quotes in the JSON object become strings in Python.
  • Boolean true and false become True and False in Python.
  • Finally, numbers without quotes around them become numbers in Python.

Now let’s work on the code to read the incoming JSON data.

First, let’s assign everything from the JSON object into a variable using request.get_json().

request.get_json() converts the JSON object into Python data. Let’s assign the incoming request data to variables and return them by making the following changes to the json-example route:

app.py
# GET requests will be blocked
@app.route('/json-example', methods=['POST'])
def json_example():
    request_data = request.get_json()

    language = request_data['language']
    framework = request_data['framework']

    # two keys are needed because of the nested object
    python_version = request_data['version_info']['python']

    # an index is needed because of the array
    example = request_data['examples'][0]

    boolean_test = request_data['boolean_test']

    return '''
           The language value is: {}
           The framework value is: {}
           The Python version is: {}
           The item at index 0 in the example list is: {}
           The boolean value is: {}'''.format(language, framework, python_version, example, boolean_test)

Note how you access elements that aren’t at the top level. ['version']['python'] is used because you are entering a nested object. And ['examples'][0] is used to access the 0th index in the examples array.

If the JSON object sent with the request doesn’t have a key that is accessed in your view function, then the request will fail. If you don’t want it to fail when a key doesn’t exist, you’ll have to check if the key exists before trying to access it.

app.py
# GET requests will be blocked
@app.route('/json-example', methods=['POST'])
def json_example():
    request_data = request.get_json()

    language = None
    framework = None
    python_version = None
    example = None
    boolean_test = None

    if request_data:
        if 'language' in request_data:
            language = request_data['language']

        if 'framework' in request_data:
            framework = request_data['framework']

        if 'version_info' in request_data:
            if 'python' in request_data['version_info']:
                python_version = request_data['version_info']['python']

        if 'examples' in request_data:
            if (type(request_data['examples']) == list) and (len(request_data['examples']) > 0):
                example = request_data['examples'][0]

        if 'boolean_test' in request_data:
            boolean_test = request_data['boolean_test']

    return '''
           The language value is: {}
           The framework value is: {}
           The Python version is: {}
           The item at index 0 in the example list is: {}
           The boolean value is: {}'''.format(language, framework, python_version, example, boolean_test)

Run the app and submit the example JSON request using Postman. In the response, you will get the following output:

Output
The language value is: Python The framework value is: Flask The Python version is: 3.9 The item at index 0 in the example list is: query The boolean value is: false

Now you understand handling JSON objects.

Conclusion

In this article, you built a Flask application with three routes that accept either query strings, form data, or JSON objects.

Also, recall that all approaches had to address the recurring consideration for gracefully failing when a key is missing.

Warning: One topic that was not covered in this article was sanitizing user input. Sanitizing user input would ensure that data read by the application would not cause something to fail unexpectedly or bypass security measures.

If you’d like to learn more about Flask, check out our Flask topic page for exercises and programming projects.

Creative Commons License