Book Image

Python API Development Fundamentals

By : Jack Chan, Ray Chung, Jack Huang
Book Image

Python API Development Fundamentals

By: Jack Chan, Ray Chung, Jack Huang

Overview of this book

Python is a flexible language that can be used for much more than just script development. By knowing the Python RESTful APIs work, you can build a powerful backend for web applications and mobile applications using Python. You'll take your first steps by building a simple API and learning how the frontend web interface can communicate with the backend. You'll also learn how to serialize and deserialize objects using the marshmallow library. Then, you'll learn how to authenticate and authorize users using Flask-JWT. You'll also learn how to enhance your APIs by adding useful features, such as email, image upload, searching, and pagination. You'll wrap up the whole book by deploying your APIs to the cloud. By the end of this book, you'll have the confidence and skill to leverage the power of RESTful APIs and Python to build efficient web applications.
Table of Contents (12 chapters)

Using curl or httpie to Test All the Endpoints

In this section, we will go through ways to test the API service endpoints in our recipe management application using Command Prompt. Testing is a very important step in application development. This is to ensure the functions we developed are working as expected. We can use curl or httpie, depending on your personal preference. In the subsequent exercise, we will show you both tools.

Curl (or cURL) is a command-line tool that can transfer data using URLs. We can use this tool to send requests to our API endpoints and examine the response. If you are running on macOS, you don't need to install curl. It is pre-installed in the system and you can find it in Terminal. You can also run it in the Terminal in PyCharm. However, if you are running on Windows, you need to download and install it for free from http://curl.haxx.se/download.html.

Httpie (aych-tee-tee-pie) is another command-line client that does a similar thing. It was built with the goal to improve the communication between the CLI (command-line interface) and the web. It is pretty user-friendly. For more details about httpie, please refer to https://httpie.org/.

We added httpie==1.0.2 in our requirements.txt previously, so PyCharm should have already installed it for us. The main benefit of having httpie is it will beautifully format the JSON document, making it more readable. And believe me, that will save us a lot of time when we move on to verifying the HTTP response from the server.

Exercise 3: Testing Our API Endpoints with httpie and curl

In this exercise, we are going to use httpie and curl to test our API endpoints. We will test the functions of getting all the recipes back from the server, and also creating/updating the recipes:

  1. We will first open the Terminal in PyCharm. It is located at the bottom of the application. It will look as shown in the following screenshot:
    Figure 1.9: PyCharm Terminal
    Figure 1.9: PyCharm Terminal
  2. Type in the following httpie command to get the recipes from our API endpoint, http://localhost:5000/recipes; we will be using the HTTP GET method here:
    http GET localhost:5000/recipes
  3. If you prefer to do it the curl way, use the following command instead. Note that we have different parameters here: -i is for showing the header in the response and -X is for specifying the HTTP method. We will be using GET here:
    curl -i -X GET localhost:5000/recipes 

    Note

    The http GET and curl-i -X GET commands basically do the same thing, which is using the HTTP GET method to send a request to http://localhost:5000/recipes. If the code that we put in on the server-side is working properly, the request will go through the /recipes route and the get_recipes function will be invoked. This will then get us all the recipes in JSON format.

    Take a look at the response we get. The first few lines in the response are the header. It has the HTTP status 200 OK and a Content-Length of 175 bytes. The Content-Type is application/json and, in the end, we have the response body in JSON format:

    HTTP/1.0 200 OK
    Content-Length: 175
    Content-Type: application/json
    Date: Mon, 15 Jul 2019 12:40:44 GMT
    Server: Werkzeug/0.15.4 Python/3.7.0
    {
        "data": [
            {
                "description": "This is a lovely egg salad recipe.",
                "id": 1,
                "name": "Egg Salad"
            },
            {
                "description": "This is a lovely tomato pasta recipe.",
                "id": 2,
                "name": "Tomato Pasta"
            }
        ]
    }
  4. After that, let's create a recipe. This time, use the HTTP POST method, as we have lots of information that cannot be encoded in the URL. Please take a look at the following httpie command:
    http POST localhost:5000/recipes name="Cheese Pizza" description="This is a lovely cheese pizza"
  5. And then following is the curl command. The -H here is to specify the header in the request. Put in Content-Type: application/json, as we are going to send over the details of the new recipe in JSON format. The -d here is to specify the HTTP POST data, which is our new recipe:
    curl -i -X POST localhost:5000/recipes -H "Content-Type: application/json" -d '{"name":"Cheese Pizza", "description":"This is a lovely cheese pizza"}'
  6. The @app.route('/recipes', methods=['POST']) in the backend to catch this client request and invoke the create_recipe function. It will get the recipe details from the client request and save it to a list in the application memory. Once the recipe is successfully stored in the memory, it will return an HTTP status of 201 CREATED, and the new recipe will also be returned in the HTTP response for us to verify:
    HTTP/1.0 201 CREATED
    Content-Length: 77
    Content-Type: application/json
    Date: Mon, 15 Jul 2019 14:26:11 GMT
    Server: Werkzeug/0.15.4 Python/3.7.0
    {
        "description": "This is a lovely cheese pizza",
        "id": 3,
        "name": "Cheese Pizza"
    }
  7. Now, get all the recipes again to verify if our previous recipe was really created successfully. We expect to receive three recipes in the response now:
    http GET localhost:5000/recipes 
    curl -i -X GET localhost:5000/recipes 
  8. Use either one of the preceding commands. They do the same thing, which is to trigger the get_recipes function and get us all the recipes currently stored in the application memory in JSON format.

    In the following response, we can see that the HTTP header is saying OK, and the Content-Length is now slightly longer than our previous response, that is, 252 bytes. This makes sense because we are expecting to see one more recipe in the response. The Content-Type is again application/json, with the body storing the recipes in JSON format. Now we can see our new recipe with ID 3:

    HTTP/1.0 200 OK
    Content-Length: 252
    Content-Type: application/json
    Date: Tue, 16 Jul 2019 01:55:30 GMT
    Server: Werkzeug/0.15.4 Python/3.7.0
    {
        "data": [
            {
                "description": "This is a lovely egg salad recipe.",
                "id": 1,
                "name": "Egg Salad"
            },
            {
                "description": "This is a lovely tomato pasta recipe.",
                "id": 2,
                "name": "Tomato Pasta"
            },
            {
                "description": "This is a lovely cheese pizza",
                "id": 3,
                "name": "Cheese Pizza"
            }
        ]
    }
  9. Cool! So far, we are in pretty good shape. Now, test our application by trying to modify the recipe with ID 3. Use the HTTP PUT method and send over the modified name and description of the recipe to localhost:5000/recipes/3:
    http PUT localhost:5000/recipes/3 name="Lovely Cheese Pizza" description="This is a lovely cheese pizza recipe."

    The following is the curl command. Again, -H is to specify the header in the HTTP request, and we are setting that to "Content-Type: application/json"; -d is to specify that our data should be in JSON format:

    curl -i -X PUT localhost:5000/recipes/3 -H "Content-Type: application/json" -d '{"name":"Lovely Cheese Pizza", "description":"This is a lovely cheese pizza recipe."}'
  10. If things are working properly, then the client request will be caught by the @app.route('/recipes/<int:recipe_id>', methods=['PUT']) route. It will then invoke the update_recipe(recipe_id) function to look for the recipe with the passed-in recipe_id, update it, and return it. Together with the updated recipe in JSON format, we will also receive the HTTP status of OK (200):
    HTTP/1.0 200 OK
    Content-Length: 92
    Content-Type: application/json
    Date: Tue, 16 Jul 2019 02:04:57 GMT
    Server: Werkzeug/0.15.4 Python/3.7.0
    {
        "description": "This is a lovely cheese pizza recipe.",
        "id": 3,
        "name": "Lovely Cheese Pizza"
    }
  11. Alright, all good so far. Now, go on and see if we can get a particular recipe. To do this, send a request to localhost:5000/recipes/3 to get the recipe with ID 3, and confirm whether our previous update was successful:
    http GET localhost:5000/recipes/3

    We can also use a curl command:

    curl -i -X GET localhost:5000/recipes/3 
  12. The application will look for the recipe with the recipe_id and return it in JSON format, together with an HTTP status of 200 OK:
    HTTP/1.0 200 OK
    Content-Length: 92
    Content-Type: application/json
    Date: Tue, 16 Jul 2019 06:10:49 GMT
    Server: Werkzeug/0.15.4 Python/3.7.0
    {
        "description": "This is a lovely cheese pizza recipe.",
        "id": 3,
        "name": "Lovely Cheese Pizza"
    }
  13. Now, what if we try a recipe ID that we know doesn't exist? How will the application behave? Test it out with the httpie command as follows:
    http GET localhost:5000/recipes/101

    Alternatively, use the following curl command, which will do the same thing as in the preceding code:

    curl -i -X GET localhost:5000/recipes/101 
  14. Similarly, @app.route('/recipes/<int:recipe_id>', methods=['GET']) in the application will catch this client request and try to look for the recipe with ID = 101. The application will return with an HTTP status of 404 and a message: "recipe not found" in JSON format:
    HTTP/1.0 404 NOT FOUND
    Content-Length: 31
    Content-Type: application/json
    Date: Tue, 16 Jul 2019 06:15:31 GMT
    Server: Werkzeug/0.15.4 Python/3.7.0
    {
        "message": "recipe not found"
    }

If your application passed the test, congratulations! It is a pretty solid implementation. You can choose to perform more tests by yourself if you want to.