How does Python store data in server?

Fast forward to now, when the same creeping memory usage was appearing. This was, once again, due to our rapid growth. We saw that traffic to PersonDB almost doubled in volume in four months. In addition, the dataset for PersonDB more than doubled in size. This was causing us to hit our resource capacities again.

We knew that the memory usage pattern hinted at the CoW issue that we saw a couple of months ago. We decided that we could invest a little more time into PersonDB and solve the “memory leak” for good. But once again, we were constrained by time. We had to solve this before investing in the next-generation service so that downstream services (and more importantly, our customers) wouldn’t be affected.

When running PersonDB to study the htop and top outputs, we saw that even though there was one worker, it was indeed seeing a bunch of CoW faults. Once again, Copy-On-Write faults didn’t make sense for a read-only database.

It helps to understand exactly why CoW faults were happening here. After a bit of digging, we found this article by Instagram, where they figured out that Copy-On-Read was happening on their pre-fork server. Copy-On-Read means that the memory was being copied by the child processes/threads upon being accessed—which is precisely what we were seeing in PersonDB.

Copy-On-Read was occuring because Python keeps a reference count for every object for the sake of garbage collection. These reference counts are located within the data structure representing the object. In effect:

  • Each object in PersonDB had a reference count, managed by the Python runtime.

  • Each worker thread was accessing PersonDB data in the coordinator thread. They used this data to process incoming requests.

  • Each time a memory page on the coordinator thread was accessed, the Python runtime incremented those reference counts.

  • Incrementing the reference count means that the variable has to get overwritten.

  • Because the worker thread was writing to those reference counts in the coordinator process’ memory, a CoW fault gets triggered.

We, unfortunately, did not have the runway to work around the Python Garbage Collector. In the time that it would take to do a deeper dive into the CoW issue, we could have even made significant progress in re-architecting PersonDB into its next generation. One of the biggest learnings from my time at Abnormal is that it’s worth the time and effort to identify the highest ROI problems before jumping in to solve them.

Deepanshu founded ListenData with a simple objective - Make analytics easy to understand and follow. He has over 10 years of experience in data science. During his tenure, he has worked with global clients in various domains like Banking, Insurance, Private Equity, Telecom and Human Resource.

There’s an amazing amount of data available on the Web. Many web services, like YouTube and GitHub, make their data accessible to third-party applications through an application programming interface (API). One of the most popular ways to build APIs is the REST architecture style. Python provides some great tools not only to get data from REST APIs but also to build your own Python REST APIs.

In this tutorial, you’ll learn:

  • What REST architecture is
  • How REST APIs provide access to web data
  • How to consume data from REST APIs using the
    >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}
    
    >>> todo = {"userId": 1, "title": "Wash car", "completed": True}
    >>> response = requests.put(api_url, json=todo)
    >>> response.json()
    {'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
    
    >>> response.status_code
    200
    
    3 library
  • What steps to take to build a REST API
  • What some popular Python tools are for building REST APIs

By using Python and REST APIs, you can retrieve, parse, update, and manipulate the data provided by any web service you’re interested in.

Free Bonus: Click here to download a copy of the "REST API Examples" Guide and get a hands-on introduction to Python + REST API principles with actionable examples.

REST Architecture

REST stands for representational state transfer and is a software architecture style that defines a pattern for client and server communications over a network. REST provides a set of constraints for software architecture to promote performance, scalability, simplicity, and reliability in the system.

REST defines the following architectural constraints:

  • Stateless: The server won’t maintain any state between requests from the client.
  • Client-server: The client and server must be decoupled from each other, allowing each to develop independently.
  • Cacheable: The data retrieved from the server should be cacheable either by the client or by the server.
  • Uniform interface: The server will provide a uniform interface for accessing resources without defining their representation.
  • Layered system: The client may access the resources on the server indirectly through other layers such as a proxy or load balancer.
  • Code on demand (optional): The server may transfer code to the client that it can run, such as JavaScript for a single-page application.

Note, REST is not a specification but a set of guidelines on how to architect a network-connected software system.

Remove ads

REST APIs and Web Services

A REST web service is any web service that adheres to REST architecture constraints. These web services expose their data to the outside world through an API. REST APIs provide access to web service data through public web URLs.

For example, here’s one of the URLs for GitHub’s REST API:

https://api.github.com/users/<username>

This URL allows you to access information about a specific GitHub user. You access data from a REST API by sending an to a specific URL and processing the response.

HTTP Methods

REST APIs listen for HTTP methods like

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4,
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5, and
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
6 to know which operations to perform on the web service’s resources. A resource is any data available in the web service that can be accessed and manipulated with HTTP requests to the REST API. The HTTP method tells the API which action to perform on the resource.

While there are many HTTP methods, the five methods listed below are the most commonly used with REST APIs:

HTTP methodDescription

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4Retrieve an existing resource.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5Create a new resource.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9Update an existing resource.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
0Partially update an existing resource.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
6Delete a resource.

A REST API client application can use these five HTTP methods to manage the state of resources in the web service.

Status Codes

Once a REST API receives and processes an HTTP request, it will return an HTTP response. Included in this response is an HTTP status code. This code provides information about the results of the request. An application sending requests to the API can check the status code and perform actions based on the result. These actions could include handling errors or displaying a success message to a user.

Below is a list of the most common status codes returned by REST APIs:

CodeMeaningDescription

{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
2OKThe requested action was successful.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
3CreatedA new resource was created.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
4AcceptedThe request was received, but no modification has been made yet.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
5No ContentThe request was successful, but the response has no content.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
6Bad RequestThe request was malformed.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
7UnauthorizedThe client is not authorized to perform the requested action.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
8Not FoundThe requested resource was not found.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
9Unsupported Media TypeThe request data format is not supported by the server.
https://api.example.com/customers
00Unprocessable EntityThe request data was properly formatted but contained invalid or missing data.
https://api.example.com/customers
01Internal Server ErrorThe server threw an error when processing the request.

These ten status codes represent only a small subset of the available HTTP status codes. Status codes are numbered based on the category of the result:

Code rangeCategory

https://api.example.com/customers
02Successful operation
https://api.example.com/customers
03Redirection
https://api.example.com/customers
04Client error
https://api.example.com/customers
05Server error

HTTP status codes come in handy when working with REST APIs as you’ll often need to perform different logic based on the results of the request.

API Endpoints

A REST API exposes a set of public URLs that client applications use to access the resources of a web service. These URLs, in the context of an API, are called endpoints.

To help clarify this, take a look at the table below. In this table, you’ll see API endpoints for a hypothetical CRM system. These endpoints are for a customer resource that represents potential

https://api.example.com/customers
06 in the system:

HTTP methodAPI endpointDescription

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4
https://api.example.com/customers
08Get a list of customers.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4
https://api.example.com/customers
10Get a single customer.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5
https://api.example.com/customers
08Create a new customer.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9
https://api.example.com/customers
10Update a customer.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
0
https://api.example.com/customers
10Partially update a customer.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
6
https://api.example.com/customers
10Delete a customer.

Each of the endpoints above performs a different action based on the HTTP method.

Note: The base URL for the endpoints has been omitted for brevity. In reality, you’ll need the full URL path to access an API endpoint:

https://api.example.com/customers

This is the full URL you’d use to access this endpoint. The base URL is everything besides

https://api.example.com/customers
08.

You’ll note that some endpoints have

https://api.example.com/customers
20 at the end. This notation means you need to append a numeric
https://api.example.com/customers
21 to the URL to tell the REST API which
https://api.example.com/customers
22 you’d like to work with.

The endpoints listed above represent only one resource in the system. Production-ready REST APIs often have tens or even hundreds of different endpoints to manage the resources in the web service.

Remove ads

REST and Python: Consuming APIs

To write code that interacts with REST APIs, most Python developers turn to

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
3 to send HTTP requests. This library abstracts away the complexities of making HTTP requests. It’s one of the few projects worth treating as if it’s part of the standard library.

To start using

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
3, you need to install it first. You can use
https://api.example.com/customers
25 to install it:

$ python -m pip install requests

Now that you’ve got

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
3 installed, you can start sending HTTP requests.

GET

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 is one of the most common HTTP methods you’ll use when working with REST APIs. This method allows you to retrieve resources from a given API.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 is a read-only operation, so you shouldn’t use it to modify an existing resource.

To test out

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 and the other methods in this section, you’ll use a service called JSONPlaceholder. This free service provides fake API endpoints that send back responses that
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
3 can process.

To try this out, start up the Python REPL and run the following commands to send a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 request to a JSONPlaceholder endpoint:

>>>

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}

This code calls

https://api.example.com/customers
32 to send a
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 request to
https://api.example.com/customers
34, which responds with the
https://api.example.com/customers
35 item with the ID
https://api.example.com/customers
36. Then you can call on the
https://api.example.com/customers
38 object to view the data that came back from the API.

The response data is formatted as JSON, a key-value store similar to a Python dictionary. It’s a very popular data format and the de facto interchange format for most REST APIs.

Beyond viewing the JSON data from the API, you can also view other things about the

https://api.example.com/customers
38:

>>>

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'

Here, you access

https://api.example.com/customers
40 to see the HTTP status code. You can also view the response’s HTTP headers with
https://api.example.com/customers
41. This dictionary contains metadata about the response, such as the
https://api.example.com/customers
42 of the response.

POST

Now, take a look at how you use

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
3 to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 data to a REST API to create a new resource. You’ll use JSONPlaceholder again, but this time you’ll include JSON data in the request. Here’s the data that you’ll send:

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}

This JSON contains information for a new

https://api.example.com/customers
35 item. Back in the Python REPL, run the following code to create the new
https://api.example.com/customers
35:

>>>

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> response = requests.post(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201

Here, you call

https://api.example.com/customers
47 to create a new
https://api.example.com/customers
35 in the system.

First, you create a dictionary containing the data for your

https://api.example.com/customers
35. Then you pass this dictionary to the
https://api.example.com/customers
50 keyword argument of
https://api.example.com/customers
47. When you do this,
https://api.example.com/customers
47 automatically sets the request’s HTTP header
https://api.example.com/customers
42 to
https://api.example.com/customers
54. It also serializes
https://api.example.com/customers
35 into a JSON string, which it appends to the body of the request.

If you don’t use the

https://api.example.com/customers
50 keyword argument to supply the JSON data, then you need to set
https://api.example.com/customers
42 accordingly and serialize the JSON manually. Here’s an equivalent version to the previous code:

>>>

>>> import requests
>>> import json
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> headers =  {"Content-Type":"application/json"}
>>> response = requests.post(api_url, data=json.dumps(todo), headers=headers)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201

In this code, you add a

https://api.example.com/customers
58 dictionary that contains a single header
https://api.example.com/customers
42 set to
https://api.example.com/customers
54. This tells the REST API that you’re sending JSON data with the request.

You then call

https://api.example.com/customers
47, but instead of passing
https://api.example.com/customers
35 to the
https://api.example.com/customers
50 argument, you first call
https://api.example.com/customers
64 to serialize it. After it’s serialized, you pass it to the
https://api.example.com/customers
65 keyword argument. The
https://api.example.com/customers
65 argument tells
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
3 what data to include in the request. You also pass the
https://api.example.com/customers
58 dictionary to
https://api.example.com/customers
47 to set the HTTP headers manually.

When you call

https://api.example.com/customers
47 like this, it has the same effect as the previous code but gives you more control over the request.

Note: comes from the

https://api.example.com/customers
50 package in the standard library. This package provides useful methods for working with JSON in Python.

Once the API responds, you call

https://api.example.com/customers
73 to view the JSON. The JSON includes a generated
https://api.example.com/customers
74 for the new
https://api.example.com/customers
35. The
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
3 status code tells you that a new resource was created.

Remove ads

PUT

Beyond

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 and
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5,
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
3 provides support for all the other HTTP methods you would use with a REST API. The following code sends a
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9 request to update an existing
https://api.example.com/customers
35 with new data. Any data sent with a
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9 request will completely replace the existing values of the
https://api.example.com/customers
35.

You’ll use the same JSONPlaceholder endpoint you used with

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 and
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5, but this time you’ll append
https://api.example.com/customers
86 to the end of the URL. This tells the REST API which
https://api.example.com/customers
35 you’d like to update:

>>>

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200

Here, you first call

https://api.example.com/customers
32 to view the contents of the existing
https://api.example.com/customers
35. Next, you call
https://api.example.com/customers
90 with new JSON data to replace the existing to-do’s values. You can see the new values when you call
https://api.example.com/customers
73. Successful
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9 requests will always return
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
2 instead of
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
3 because you aren’t creating a new resource but just updating an existing one.

PATCH

Next up, you’ll use

https://api.example.com/customers
95 to modify the value of a specific field on an existing
https://api.example.com/customers
35.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
0 differs from
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9 in that it doesn’t completely replace the existing resource. It only modifies the values set in the JSON sent with the request.

You’ll use the same

https://api.example.com/customers
35 from the last example to try out
https://api.example.com/customers
95. Here are the current values:

{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

Now you can update the

$ python -m pip install requests
01 with a new value:

>>>

https://api.example.com/customers
0

When you call

https://api.example.com/customers
73, you can see that
$ python -m pip install requests
01 was updated to
$ python -m pip install requests
04.

DELETE

Last but not least, if you want to completely remove a resource, then you use

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
6. Here’s the code to remove a
https://api.example.com/customers
35:

>>>

https://api.example.com/customers
1

You call

$ python -m pip install requests
07 with an API URL that contains the ID for the
https://api.example.com/customers
35 you would like to remove. This sends a
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
6 request to the REST API, which then removes the matching resource. After deleting the resource, the API sends back an empty JSON object indicating that the resource has been deleted.

The

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
3 library is an awesome tool for working with REST APIs and an indispensable part of your Python tool belt. In the next section, you’ll change gears and consider what it takes to build a REST API.

REST and Python: Building APIs

REST API design is a huge topic with many layers. As with most things in technology, there’s a wide range of opinions on the best approach to building APIs. In this section, you’ll look at some recommended steps to follow as you build an API.

Identify Resources

The first step you’ll take as you build a REST API is to identify the resources the API will manage. It’s common to describe these resources as plural nouns, like

https://api.example.com/customers
06,
$ python -m pip install requests
12, or
$ python -m pip install requests
13. As you identify different resources in your web service, you’ll build out a list of nouns that describe the different data users can manage in the API.

As you do this, make sure to consider any nested resources. For example,

https://api.example.com/customers
06 may have
$ python -m pip install requests
15, or
$ python -m pip install requests
12 may contain
$ python -m pip install requests
17. Establishing these resource hierarchies will help when you define API endpoints.

Remove ads

Define Your Endpoints

Once you’ve identified the resources in your web service, you’ll want to use these to define the API endpoints. Here are some example endpoints for a

$ python -m pip install requests
13 resource you might find in an API for a payment processing service:

HTTP methodAPI endpointDescription

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4
$ python -m pip install requests
20Get a list of transactions.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4
$ python -m pip install requests
22Get a single transaction.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5
$ python -m pip install requests
20Create a new transaction.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9
$ python -m pip install requests
22Update a transaction.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
0
$ python -m pip install requests
22Partially update a transaction.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
6
$ python -m pip install requests
22Delete a transaction.

These six endpoints cover all the operations that you’ll need to create, read, update, and delete

$ python -m pip install requests
13 in the web service. Each resource in your web service would have a similar list of endpoints based on what actions a user can perform with the API.

Note: An endpoint shouldn’t contain verbs. Instead, you should select the appropriate HTTP methods to convey the endpoint’s action. For example, the endpoint below contains an unneeded verb:

https://api.example.com/customers
2

Here,

$ python -m pip install requests
32 is included in the endpoint when it isn’t needed. The HTTP method
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 already provides the semantic meaning for the endpoint by indicating the action. You can remove
$ python -m pip install requests
32 from the endpoint:

https://api.example.com/customers
3

This endpoint contains only a plural noun, and the HTTP method

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 communicates the action.

Now take a look at an example of endpoints for a nested resource. Here, you’ll see endpoints for

$ python -m pip install requests
17 that are nested under
$ python -m pip install requests
12 resources:

HTTP methodAPI endpointDescription

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4
$ python -m pip install requests
39Get a list of guests.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4
$ python -m pip install requests
41Get a single guest.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5
$ python -m pip install requests
39Create a new guest.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9
$ python -m pip install requests
41Update a guest.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
0
$ python -m pip install requests
41Partially update a guest.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
6
$ python -m pip install requests
41Delete a guest.

With these endpoints, you can manage

$ python -m pip install requests
17 for a specific event in the system.

This isn’t the only way to define an endpoint for nested resources. Some people prefer to use query strings to access a nested resource. A query string allows you to send additional parameters with your HTTP request. In the following endpoint, you append a query string to get

$ python -m pip install requests
17 for a specific
$ python -m pip install requests
52:

https://api.example.com/customers
4

This endpoint will filter out any

$ python -m pip install requests
17 that don’t reference the given
$ python -m pip install requests
52. As with many things in API design, you need to decide which method fits your web service best.

Note: It’s very unlikely that your REST API will stay the same throughout the life of your web service. Resources will change, and you’ll need to update your endpoints to reflect these changes. This is where API versioning comes in. API versioning allows you to modify your API without fear of breaking existing integrations.

There’s a wide range of versioning strategies. Selecting the right option depends on the requirements of your API. Below are some of the most popular options for API versioning:

No matter what strategy you select, versioning your API is an important step to ensuring it can adapt to changing requirements while supporting existing users.

Now that you’ve covered endpoints, in the next section you’ll look at some options for formatting data in your REST API.

Pick Your Data Interchange Format

Two popular options for formatting web service data are XML and JSON. Traditionally, XML was very popular with SOAP APIs, but JSON is more popular with REST APIs. To compare the two, take a look at an example

$ python -m pip install requests
55 resource formatted as XML and JSON.

Here’s the book formatted as XML:

https://api.example.com/customers
5

XML uses a series of elements to encode data. Each element has an opening and closing tag, with the data in between. Elements can be nested inside other elements. You can see this above, where several

$ python -m pip install requests
56 tags are nested inside of
$ python -m pip install requests
57.

Now, take a look at the same

$ python -m pip install requests
55 in JSON:

https://api.example.com/customers
6

JSON stores data in key-value pairs similar to a Python dictionary. Like XML, JSON supports nesting data to any level, so you can model complex data.

Neither JSON nor XML is inherently better than the other, but there’s a preference for JSON among REST API developers. This is especially true when you pair a REST API with a front-end framework like React or Vue.

Remove ads

Design Success Responses

Once you’ve picked a data format, the next step is to decide how you’ll respond to HTTP requests. All responses from your REST API should have a similar format and include the proper HTTP status code.

In this section, you’ll look at some example HTTP responses for a hypothetical API that manages an inventory of

$ python -m pip install requests
59. These examples will give you a sense of how you should format your API responses. To make things clear, you’ll look at raw HTTP requests and responses instead of using an HTTP library like
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
3.

To start things off, take a look at a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 request to
$ python -m pip install requests
62, which returns a list of
$ python -m pip install requests
59:

https://api.example.com/customers
7

This HTTP request is made up of four parts:

  1. >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}
    
    >>> todo = {"userId": 1, "title": "Wash car", "completed": True}
    >>> response = requests.put(api_url, json=todo)
    >>> response.json()
    {'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
    
    >>> response.status_code
    200
    
    4 is the HTTP method type.
  2. $ python -m pip install requests
    
    62 is the API endpoint.
  3. $ python -m pip install requests
    
    66 is the HTTP version.
  4. $ python -m pip install requests
    
    67 is the API host.

These four parts are all you need to send a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 request to
$ python -m pip install requests
62. Now take a look at the response. This API uses JSON as the data interchange format:

https://api.example.com/customers
8

The API returns a response that contains a list of

$ python -m pip install requests
59. You know that the response was successful because of the
$ python -m pip install requests
71 status code. The response also has a
https://api.example.com/customers
42 header set to
https://api.example.com/customers
54. This tells the user to parse the response as JSON.

Note: When you’re working with a real API, you’re going to see more HTTP headers than this. These headers differ between APIs, so they’ve been excluded in these examples.

It’s important to always set the correct

https://api.example.com/customers
42 header on your response. If you send JSON, then set
https://api.example.com/customers
42 to
https://api.example.com/customers
54. If XML, then set it to
$ python -m pip install requests
77. This header tells the user how they should parse the data.

You also want to include an appropriate status code in your response. For any successful

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 request, you should return
$ python -m pip install requests
71. This tells the user that their request was processed as expected.

Take a look at another

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 request, this time for a single car:

https://api.example.com/customers
9

This HTTP request queries the API for car

https://api.example.com/customers
36. Here’s the response:

$ python -m pip install requests
0

This response contains a single JSON object with the car’s data. Since it’s a single object, it doesn’t need to be wrapped in a list. Like the last response, this also has a

$ python -m pip install requests
71 status code.

Note: A

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 request should never modify an existing resource. If the request contains data, then this data should be ignored and the API should return the resource unchanged.

Next up, check out a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 request to add a new car:

$ python -m pip install requests
1

This

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 request includes JSON for the new car in the request. It sets the
https://api.example.com/customers
42 header to
https://api.example.com/customers
54 so the API knows the content type of the request. The API will create a new car from the JSON.

Here’s the response:

$ python -m pip install requests
2

This response has a

$ python -m pip install requests
88 status code to tell the user that a new resource was created. Make sure to use
$ python -m pip install requests
88 instead of
$ python -m pip install requests
71 for all successful
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 requests.

This response also includes a copy of the new car with an

https://api.example.com/customers
74 generated by the API. It’s important to send back an
https://api.example.com/customers
74 in the response so that the user can modify the resource again.

Note: It’s important to always send back a copy of a resource when a user creates it with

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 or modifies it with
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9 or
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
0. This way, the user can see the changes that they’ve made.

Now take a look at a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9 request:

$ python -m pip install requests
3

This request uses the

https://api.example.com/customers
74 from the previous request to update the car with all new data. As a reminder,
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9 updates all fields on the resource with new data. Here’s the response:

$ python -m pip install requests
4

The response includes a copy of the

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
00 with the new data. Again, you always want to send back the full resource for a
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9 request. The same applies to a
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
0 request:

$ python -m pip install requests
5

{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
0 requests only update a part of a resource. In the request above, the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
04 and
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
05 fields will be updated with new values. Here’s the response:

$ python -m pip install requests
6

The response contains a full copy of the

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
00. As you can see, only the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
04 and
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
05 fields have been updated.

Finally, take a look at how your REST API should respond when it receives a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
6 request. Here’s a
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
6 request to remove a
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
00:

$ python -m pip install requests
7

This

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
6 request tells the API to remove the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
00 with the ID
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
14. Here’s the response:

$ python -m pip install requests
8

This response only includes the status code

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
15. This status code tells a user that the operation was successful, but no content was returned in the response. This makes sense since the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
00 has been deleted. There’s no reason to send a copy of it back in the response.

The responses above work well when everything goes as planned, but what happens if there’s a problem with the request? In the next section, you’ll look at how your REST API should respond when errors occur.

Remove ads

Design Error Responses

There’s always a chance that requests to your REST API could fail. It’s a good idea to define what an error response will look like. These responses should include a description of what error occurred along with the appropriate status code. In this section, you’ll look at a few examples.

To start, take a look at a request for a resource that doesn’t exist in the API:

$ python -m pip install requests
9

Here, the user sends a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 request to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
18, which doesn’t exist. The API sends back the following response:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
0

This response includes a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
19 status code. Along with this, the response contains a JSON object with a descriptive error message. Providing a descriptive error message gives the user more context for the error.

Now take a look at the error response when the user sends an invalid request:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
1

This

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 request contains JSON, but it isn’t formatted correctly. It’s missing a closing curly brace (
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
21) at the end. The API won’t be able to process this data. The error response tells the user about the issue:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
2

This response includes a descriptive error message along with the

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
22 status code, telling the user they need to fix the request.

There are several other ways that the request can be wrong even if it’s formatted properly. In this next example, the user sends a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 request but includes an unsupported media type:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
3

In this request, the user sends XML, but the API only supports JSON. The API responds with this:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
4

This response includes the

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
24 status code to indicate that the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 request included a data format that isn’t supported by the API. This error code makes sense for data that’s in the wrong format, but what about data that’s invalid even with the correct format?

In this next example, the user sends a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 request but includes
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
00 data that doesn’t match fields of the other data:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
5

In this request, the user adds

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
28 and
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
29 fields to the JSON. These fields aren’t supported by the API, so it responds with an error message:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
6

This response includes the

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
30 status code. This status code indicates that there weren’t any issues with the request, but the data was invalid. A REST API needs to validate incoming data. If the user sends data with the request, then the API should validate the data and inform the user of any errors.

Responding to requests, both successful and erroneous, is one of the most important jobs of a REST API. If your API is intuitive and provides accurate responses, then it’ll be easier for users to build applications around your web service. Luckily, some great Python web frameworks abstract away the complexities of processing HTTP requests and returning responses. You’ll look at three popular options in the next section.

Remove ads

REST and Python: Tools of the Trade

In this section, you’ll look at three popular frameworks for building REST APIs in Python. Each framework has pros and cons, so you’ll have to evaluate which works best for your needs. To this end, in the next sections, you’ll look at a REST API in each framework. All the examples will be for a similar API that manages a collection of countries.

Each country will have the following fields:

  • >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    31 is the name of the country.
  • >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    32 is the capital of the country.
  • >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    33 is the area of the country in square kilometers.

The fields

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
31,
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
32, and
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
33 store data about a specific country somewhere in the world.

Most of the time, data sent from a REST API comes from a database. Connecting to a database is beyond the scope of this tutorial. For the examples below, you’ll store your data in a Python list. The exception to this is the Django REST framework example, which runs off the SQLite database that Django creates.

Note: It’s advised that you create individual folders for each of the examples to separate the source files. You’ll also want to use virtual environments to isolate dependencies.

To keep things consistent, you’ll use

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 as your main endpoint for all three frameworks. You’ll also use JSON as your data format for all three frameworks.

Now that you’ve got the background for the API, you can move on to the next section, where you’ll look at the REST API in Flask.

Flask

Flask is a Python microframework used to build web applications and REST APIs. Flask provides a solid backbone for your applications while leaving many design choices up to you. Flask’s main job is to handle HTTP requests and route them to the appropriate function in the application.

Note: The code in this section uses the new Flask 2 syntax. If you’re running an older version of Flask, then use instead of and .

To handle

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 requests in older versions of Flask, you’ll also need to add the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
42 parameter to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
43:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
7

This route handles

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 requests to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
45 in Flask 1.

Below is an example Flask application for the REST API:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
8

This application defines the API endpoint

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
45 to manage the list of countries. It handles two different kinds of requests:

  1. >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    47 returns the list of
    >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    37.
  2. >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    49 adds a new
    >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    50 to the list.

Note: This Flask application includes functions to handle only two types of requests to the API endpoint,

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
45. In a full REST API, you’d want to expand this to include functions for all the required operations.

You can try out this application by installing

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
52 with
https://api.example.com/customers
25:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
9

Once

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
52 is installed, save the code in a file called
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
55. To run this Flask application, you first need to set an environment variable called
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
56 to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
55. This tells Flask which file contains your application.

Run the following command inside the folder that contains

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
55:

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
0

This sets

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
56 to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
55 in the current shell. Optionally, you can set
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
61 to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
62, which puts Flask in debug mode:

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
1

Besides providing helpful error messages, debug mode will trigger a reload of the application after all code changes. Without debug mode, you’d have to restart the server after every change.

Note: The above commands work on macOS or Linux. If you’re running this on Windows, then you need to set

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
56 and
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
61 like this in the Command Prompt:

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
2

Now

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
56 and
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
61 are set inside the Windows shell.

With all the environment variables ready, you can now start the Flask development server by calling

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
67:

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
3

This starts up a server running the application. Open up your browser and go to

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
68, and you’ll see the following response:

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
4

This JSON response contains the three

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 defined at the start of
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
55. Take a look at the following code to see how this works:

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
5

This code uses

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
71, a Flask route decorator, to connect
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 requests to a function in the application. When you access
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
45, Flask calls the decorated function to handle the HTTP request and return a response.

In the code above,

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
74 takes
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37, which is a Python list, and converts it to JSON with
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
76. This JSON is returned in the response.

Note: Most of the time, you can just return a Python dictionary from a Flask function. Flask will automatically convert any Python dictionary to JSON. You can see this in action with the function below:

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
6

In this code, you return the second dictionary from

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37. Flask will convert this dictionary to JSON. Here’s what you’ll see when you request
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
78:

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
7

This is the JSON version of the dictionary you returned from

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
79.

In

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
74, you need to use
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
76 because you’re returning a list of dictionaries and not just a single dictionary. Flask doesn’t automatically convert lists to JSON.

Now take a look at

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
82. This function handles
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 requests to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
45 and allows you to add a new country to the list. It uses the Flask object to get information about the current HTTP request:

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
8

This function performs the following operations:

  1. Using to check that the request is JSON
  2. Creating a new
    >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    50 instance with
    >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    88
  3. Finding the next
    https://api.example.com/customers
    
    74 and setting it on the
    >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    50
  4. Appending the new
    >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    50 to
    >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    37
  5. Returning the
    >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    50 in the response along with a
    $ python -m pip install requests
    
    88 status code
  6. Returning an error message and
    >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
    
    24 status code if the request wasn’t JSON

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
82 also calls
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
97 to determine the
https://api.example.com/customers
74 for the new
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
50:

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
9

This helper function uses a generator expression to select all the country IDs and then calls

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
00 on them to get the largest value. It increments this value by
https://api.example.com/customers
36 to get the next ID to use.

You can try out this endpoint in the shell using the command-line tool curl, which allows you to send HTTP requests from the command line. Here, you’ll add a new

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
50 to the list of
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37:

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
0

This curl command has some options that are helpful to know:

  • >>> response.status_code
    200
    
    >>> response.headers["Content-Type"]
    'application/json; charset=utf-8'
    
    04 sets the HTTP method for the request.
  • >>> response.status_code
    200
    
    >>> response.headers["Content-Type"]
    'application/json; charset=utf-8'
    
    05 adds an HTTP header to the request.
  • >>> response.status_code
    200
    
    >>> response.headers["Content-Type"]
    'application/json; charset=utf-8'
    
    06 defines the request data.

With these options set, curl sends JSON data in a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 request with the
https://api.example.com/customers
42 header set to
https://api.example.com/customers
54. The REST API returns
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
10 along with the JSON for the new
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
50 you added.

Note: In this example,

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
82 doesn’t contain any validation to confirm that the JSON in the request matches the format of
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37. Check out flask-expects-json if you’d like to validate the format of JSON in Flask.

You can use curl to send a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 request to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
45 to confirm that the new
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
50 was added. If you don’t use
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
04 in your curl command, then it sends a
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 request by default:

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
1

This returns the full list of countries in the system, with the newest country at the bottom.

This is just a sampling of what Flask can do. This application could be expanded to include endpoints for all the other HTTP methods. Flask also has a large ecosystem of extensions that provide additional functionality for REST APIs, such as database integrations, authentication, and background processing.

Remove ads

Django REST Framework

Another popular option for building REST APIs is Django REST framework. Django REST framework is a Django plugin that adds REST API functionality on top of an existing Django project.

To use Django REST framework, you need a Django project to work with. If you already have one, then you can apply the patterns in the section to your project. Otherwise, follow along and you’ll build a Django project and add in Django REST framework.

First, install

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
19 and
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
20 with
https://api.example.com/customers
25:

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
2

This installs

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
19 and
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
20. You can now use the
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
24 tool to create a new Django project. Run the following command to start your project:

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
3

This command creates a new folder in your current directory called

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
25. Inside this folder are all the files you need to run your Django project. Next, you’re going to create a new Django application inside your project. Django breaks up the functionality of a project into applications. Each application manages a distinct part of the project.

Note: You’re only going to scratch the surface of what Django can do in this tutorial. If you’re interested in learning more, then check out the available Django tutorials.

To create the application, change directories to

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
25 and run the following command:

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
4

This creates a new

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 folder inside your project. Inside this folder are the base files for this application.

Now that you’ve created an application to work with, you need to tell Django about it. Alongside the

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 folder that you just created is another folder called
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
25. This folder contains configurations and settings for your project.

Note: This folder has the same name as the root folder that Django created when you ran

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
30.

Open up the

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
31 file that’s inside the
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
25 folder. Add the following lines to
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
33 to tell Django about the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 application and Django REST framework:

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
5

You’ve added a line for the

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 application and
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
36.

You may be wondering why you need to add

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
36 to the applications list. You need to add it because Django REST framework is just another Django application. Django plugins are Django applications that are packaged up and distributed and that anyone can use.

The next step is to create a Django model to define the fields of your data. Inside of the

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 application, update
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
39 with the following code:

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
6

This code defines a

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40 model. Django will use this model to create the database table and columns for the country data.

Run the following commands to have Django update the database based on this model:

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
7

These commands use Django migrations to create a new table in the database.

This table starts empty, but it would be nice to have some initial data so you can test Django REST framework. To do this, you’re going to use a Django fixture to load some data in the database.

Copy and save the following JSON data into a file called

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
41 and save it inside the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 directory:

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
8

This JSON contains database entries for three countries. Call the following command to load this data in the database:

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
9

This adds three rows to the database.

With that, your Django application is all set up and populated with some data. You can now start adding Django REST framework to the project.

Django REST framework takes an existing Django model and converts it to JSON for a REST API. It does this with model serializers. A model serializer tells Django REST framework how to convert a model instance into JSON and what data to include.

You’ll create your serializer for the

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40 model from above. Start by creating a file called
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
44 inside of the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 application. Once you’ve done that, add the following code to
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
44:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> response = requests.post(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
0

This serializer,

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
47, subclasses
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
48 to automatically generate JSON content based on the model fields of
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40. Unless specified, a
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
50 subclass will include all fields from the Django model in the JSON. You can modify this behavior by setting
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
51 to a list of data you wish to include.

Just like Django, Django REST framework uses views to query data from the database to display to the user. Instead of writing REST API views from scratch, you can subclass Django REST framework’s class, which has default views for common REST API operations.

Note: The Django REST framework documentation refers to these views as .

Here’s a list of the actions that

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
52 provides and their equivalent HTTP methods:

HTTP methodActionDescription

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
55Get a list of countries.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
57Get a single country.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
59Create a new country.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
61Update a country.
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
0
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
63Partially update a country.
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
6
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
65Delete a country.

As you can see, these actions map to the standard HTTP methods you’d expect in a REST API. You can in your subclass or based on the requirements of your API.

Below is the code for a

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
52 subclass called
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
67. This class will generate the views needed to manage
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40 data. Add the following code to
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
69 inside the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 application:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> response = requests.post(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
1

In this class,

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
71 is set to
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
47 and
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
73 is set to
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
74. This tells Django REST framework which serializer to use and how to query the database for this specific set of views.

Once the views are created, they need to be mapped to the appropriate URLs or endpoints. To do this, Django REST framework provides a

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
75 that will automatically generate URLs for a
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
52.

Create a

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
77 file in the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 application and add the following code to the file:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> response = requests.post(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
2

This code creates a

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
75 and registers
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
67 under the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 URL. This will place all the URLs for
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
67 under
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
83.

Note: Django REST framework automatically appends a forward slash (

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
84) to the end of any endpoints generated by
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
75. You can disable this behavior like so:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> response = requests.post(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
3

This will disable the forward slash at the end of endpoints.

Finally, you need to update the project’s base

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
77 file to include all the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 URLs in the project. Update the
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
77 file inside of the
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
25 folder with the following code:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> response = requests.post(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
4

This puts all the URLs under

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
83. Now you’re ready to try out your Django-backed REST API. Run the following command in the root
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
25 directory to start the Django development server:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> response = requests.post(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
5

The development server is now running. Go ahead and send a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 request to
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
83 to get a list of all the countries in your Django project:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> response = requests.post(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
6

Django REST framework sends back a JSON response with the three countries you added earlier. The response above is formatted for readability, so your response will look different.

The you created in

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
95 provides URLs for requests to all the standard API endpoints:

  • >>> response.status_code
    200
    
    >>> response.headers["Content-Type"]
    'application/json; charset=utf-8'
    
    96
  • >>> response.status_code
    200
    
    >>> response.headers["Content-Type"]
    'application/json; charset=utf-8'
    
    97
  • >>> response.status_code
    200
    
    >>> response.headers["Content-Type"]
    'application/json; charset=utf-8'
    
    98
  • >>> response.status_code
    200
    
    >>> response.headers["Content-Type"]
    'application/json; charset=utf-8'
    
    99
  • {
        "userId": 1,
        "title": "Buy milk",
        "completed": false
    }
    
    00
  • {
        "userId": 1,
        "title": "Buy milk",
        "completed": false
    }
    
    01

You can try out a few more endpoints below. Send a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 request to
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
83 to a create a new
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40 in your Django project:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> response = requests.post(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
7

This creates a new

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40 with the JSON you sent in the request. Django REST framework returns a
$ python -m pip install requests
88 status code and the new
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40.

Note: By default, the response doesn’t include a new line at the end. This means that the JSON may run into your command prompt. The curl command above includes

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
08 to add a newline character after the JSON to fix this issue.

You can view an existing

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40 by sending a request to
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
97 with an existing
https://api.example.com/customers
74. Run the following command to get the first
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> response = requests.post(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
8

The response contains the information for the first

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40. These examples only covered
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 and
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 requests. Feel free to try out
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
9,
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
0, and
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
6 requests on your own to see how you can fully manage your model from the REST API.

As you’ve seen, Django REST framework is a great option for building REST APIs, especially if you have an existing Django project and you want to add an API.

Remove ads

FastAPI

FastAPI is a Python web framework that’s optimized for building APIs. It uses Python type hints and has built-in support for async operations. FastAPI is built on top of Starlette and Pydantic and is very performant.

Below is an example of the REST API built with FastAPI:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> response = requests.post(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
9

This application uses the features of FastAPI to build a REST API for the same

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
50 data you’ve seen in the other examples.

You can try this application by installing

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
20 with
https://api.example.com/customers
25:

>>> import requests
>>> import json
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> headers =  {"Content-Type":"application/json"}
>>> response = requests.post(api_url, data=json.dumps(todo), headers=headers)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
0

You’ll also need to install

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
22, a server that can run FastAPI applications:

>>> import requests
>>> import json
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> headers =  {"Content-Type":"application/json"}
>>> response = requests.post(api_url, data=json.dumps(todo), headers=headers)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
1

If you’ve installed both

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
20 and
{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
24, then save the code above in a file called
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
55. Run the following command to start up a development server:

>>> import requests
>>> import json
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> headers =  {"Content-Type":"application/json"}
>>> response = requests.post(api_url, data=json.dumps(todo), headers=headers)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
2

The server is now running. Open up a browser and go to

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
26. You’ll see FastAPI respond with this:

>>> import requests
>>> import json
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> headers =  {"Content-Type":"application/json"}
>>> response = requests.post(api_url, data=json.dumps(todo), headers=headers)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
3

FastAPI responds with a JSON array containing a list of

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37. You can also add a new country by sending a
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 request to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
45:

>>> import requests
>>> import json
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> headers =  {"Content-Type":"application/json"}
>>> response = requests.post(api_url, data=json.dumps(todo), headers=headers)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
4

You added a new country. You can confirm this with

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
47:

>>> import requests
>>> import json
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> headers =  {"Content-Type":"application/json"}
>>> response = requests.post(api_url, data=json.dumps(todo), headers=headers)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
5

FastAPI returns a JSON list including the new country you just added.

You’ll notice that the FastAPI application looks similar to the Flask application. Like Flask, FastAPI has a focused feature set. It doesn’t try to handle all aspects of web application development. It’s designed to build APIs with modern Python features.

If you look near the top of

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
55, then you’ll see a class called
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40 that extends
{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
33. The
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40 class describes the structure of the data in the REST API:

>>> import requests
>>> import json
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> headers =  {"Content-Type":"application/json"}
>>> response = requests.post(api_url, data=json.dumps(todo), headers=headers)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
6

This is an example of a Pydantic model. Pydantic models provide some helpful features in FastAPI. They use Python type annotations to enforce the data type for each field in the class. This allows FastAPI to automatically generate JSON, with the correct data types, for API endpoints. It also allows FastAPI to validate incoming JSON.

It’s helpful to highlight the first line as there’s a lot going on there:

>>> import requests
>>> import json
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> headers =  {"Content-Type":"application/json"}
>>> response = requests.post(api_url, data=json.dumps(todo), headers=headers)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
7

In this line, you see

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
35, which stores an for the ID of the
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40. It uses the from Pydantic to modify the behavior of
{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
35. In this example, you’re passing
{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
37 the keyword arguments
{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
40 and
{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
41.

The first argument,

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
40, is set to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
97. This argument specifies a function to run whenever a new
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40 is created. The return value will be assigned to
{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
35.

The second argument,

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
41, is set to
https://api.example.com/customers
74. This tells FastAPI to output the key
{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
48 instead of
{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
49 in the JSON:

>>> import requests
>>> import json
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> headers =  {"Content-Type":"application/json"}
>>> response = requests.post(api_url, data=json.dumps(todo), headers=headers)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
8

This

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
41 also means you can use
https://api.example.com/customers
74 when you create a new
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40. You can see this in the
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 list:

>>> import requests
>>> import json
>>> api_url = "https://jsonplaceholder.typicode.com/todos"
>>> todo = {"userId": 1, "title": "Buy milk", "completed": False}
>>> headers =  {"Content-Type":"application/json"}
>>> response = requests.post(api_url, data=json.dumps(todo), headers=headers)
>>> response.json()
{'userId': 1, 'title': 'Buy milk', 'completed': False, 'id': 201}

>>> response.status_code
201
9

This list contains three instances of

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40 for the initial countries in the API. Pydantic models provide some great features and allow FastAPI to easily process JSON data.

Now take a look at the two API functions in this application. The first,

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
74, returns a list of
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
37 for
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
4 requests to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
45:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
0

FastAPI will automatically create JSON based on the fields in the Pydantic model and set the right JSON data type from the Python type hints.

The Pydantic model also provides a benefit when you make a

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
5 request to
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
45. You can see in the second API function below that the parameter
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
50 has a
>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40 annotation:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
1

This type annotation tells FastAPI to validate the incoming JSON against

>>> response.status_code
200

>>> response.headers["Content-Type"]
'application/json; charset=utf-8'
40. If it doesn’t match, then FastAPI will return an error. You can try this out by making a request with JSON that doesn’t match the Pydantic model:

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}

>>> todo = {"userId": 1, "title": "Wash car", "completed": True}
>>> response = requests.put(api_url, json=todo)
>>> response.json()
{'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}

>>> response.status_code
200
2

The JSON in this request was missing a value for

>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
33, so FastAPI returned a response with the status code
>>> import requests
>>> api_url = "https://jsonplaceholder.typicode.com/todos/1"
>>> response = requests.get(api_url)
>>> response.json()
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
30 as well as details about the error. This validation is made possible by the Pydantic model.

This example only scratches the surface of what FastAPI can do. With its high performance and modern features like

{
    "userId": 1,
    "title": "Buy milk",
    "completed": false
}
66 functions and automatic documentation, FastAPI is worth considering for your next REST API.

Remove ads

Conclusion

REST APIs are everywhere. Knowing how to leverage Python to consume and build APIs allows you to work with the vast amount of data that web services provide.

In this tutorial, you’ve learned how to:

  • Identify the REST architecture style
  • Work with HTTP methods and status codes
  • Use
    >>> import requests
    >>> api_url = "https://jsonplaceholder.typicode.com/todos/10"
    >>> response = requests.get(api_url)
    >>> response.json()
    {'userId': 1, 'id': 10, 'title': 'illo est ... aut', 'completed': True}
    
    >>> todo = {"userId": 1, "title": "Wash car", "completed": True}
    >>> response = requests.put(api_url, json=todo)
    >>> response.json()
    {'userId': 1, 'title': 'Wash car', 'completed': True, 'id': 10}
    
    >>> response.status_code
    200
    
    3 to get and consume data from an external API
  • Define endpoints, data, and responses for a REST API
  • Get started with Python tools to build a REST API

Using your new Python REST API skills, you’ll be able to not only interact with web services but also build REST APIs for your applications. These tools open the door to a wide range of interesting, data-driven applications and services.

Mark as Completed

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Send Me Python Tricks »

About Jason Van Schooneveld

Jason is a software developer based in Taipei. When he's not tinkering with electronics or building Django web apps, you can find him hiking the mountains of Taiwan or brushing up on his Chinese.

» More about Jason


Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Adriana

Aldren

Geir Arne

Joanna

Jacob

Leodanis

Master Real-World Python Skills With Unlimited Access to Real Python

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

Tweet Share Share Email

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. and get answers to common questions in our support portal.

How does Python store data?

Using Python's built-in File object, it is possible to write string data to a disk file and read from it. Python's standard library, provides modules to store and retrieve serialized data in various data structures such as JSON and XML.

How is Python used in servers?

Python's role in web development can include sending data to and from servers, processing data and communicating with databases, URL routing, and ensuring security. Python offers several frameworks for web development. Commonly used ones include Django and Flask.

How to get data from server in Python?

Steps to pull data from an API using Python.
Connect to an API. At first, we need to connect to an API and make a secure connection as shown below– ... .
Get the data from API. ... .
Parse the data into JSON format. ... .
Extract the data and print it..

Is Python a server side or client side?

Server-side code can be written in any number of programming languages — examples of popular server-side web languages include PHP, Python, Ruby, C#, and JavaScript (NodeJS).