Book Image

Building RESTful Python Web Services

By : Gaston C. Hillar
Book Image

Building RESTful Python Web Services

By: Gaston C. Hillar

Overview of this book

Python is the language of choice for millions of developers worldwide, due to its gentle learning curve as well as its vast applications in day-to-day programming. It serves the purpose of building great web services in the RESTful architecture. This book will show you the best tools you can use to build your own web services. Learn how to develop RESTful APIs using the popular Python frameworks and all the necessary stacks with Python, Django, Flask, and Tornado, combined with related libraries and tools. We will dive deep into each of these frameworks to build various web services, and will provide use cases and best practices on when to use a particular framework to get the best results. We will show you everything required to successfully develop RESTful APIs with the four frameworks such as request handling, URL mapping, serialization, validation, authentication, authorization, versioning, ORMs, databases, custom code for models and views, and asynchronous callbacks. At the end of each framework, we will add authentication and security to the RESTful APIs and prepare tests for it. By the end of the book, you will have a deep understanding of the stacks needed to build RESTful web services.
Table of Contents (18 chapters)
Building RESTful Python Web Services
Credits
About the Author
Acknowledgments
About the Reviewer
www.PacktPub.com
Preface

Writing API views


Now, we will create Django views that will use the previously created GameSerializer class to return JSON representations for each HTTP request that our API will handle. Open the games/views.py file. The following lines show the initial code for this file, with just one import statement and a comment that indicates we should create the views.

from django.shortcuts import render 
 
# Create your views here. 

The following lines show the new code that creates a JSONResponse class and declares two functions: game_list and game_detail, in the games/views.py file. We are creating our first version of the API, and we use functions to keep the code as simple as possible. We will work with classes and more complex code in the next examples. The highlighted lines show the expressions that evaluate the value of the request.method attribute to determine the actions to be performed based on the HTTP verb. The code file for the sample is included in the restful_python_chapter_01_01 folder:

from django.http import HttpResponse 
from django.views.decorators.csrf import csrf_exempt 
from rest_framework.renderers import JSONRenderer 
from rest_framework.parsers import JSONParser 
from rest_framework import status 
from games.models import Game 
from games.serializers import GameSerializer 
 
 
class JSONResponse(HttpResponse): 
    def __init__(self, data, **kwargs): 
        content = JSONRenderer().render(data) 
        kwargs['content_type'] = 'application/json' 
        super(JSONResponse, self).__init__(content, **kwargs) 
 
 
@csrf_exempt 
def game_list(request): 
    if request.method == 'GET': 
        games = Game.objects.all() 
        games_serializer = GameSerializer(games, many=True) 
        return JSONResponse(games_serializer.data) 
 
    elif request.method == 'POST': 
        game_data = JSONParser().parse(request) 
        game_serializer = GameSerializer(data=game_data) 
        if game_serializer.is_valid(): 
            game_serializer.save() 
            return JSONResponse(game_serializer.data,
            status=status.HTTP_201_CREATED) 
        return JSONResponse(game_serializer.errors,
        status=status.HTTP_400_BAD_REQUEST) 
 
 
@csrf_exempt 
def game_detail(request, pk): 
    try: 
        game = Game.objects.get(pk=pk) 
    except Game.DoesNotExist: 
        return HttpResponse(status=status.HTTP_404_NOT_FOUND) 
 
    if request.method == 'GET': 
        game_serializer = GameSerializer(game) 
        return JSONResponse(game_serializer.data) 
 
    elif request.method == 'PUT': 
        game_data = JSONParser().parse(request) 
        game_serializer = GameSerializer(game, data=game_data) 
        if game_serializer.is_valid(): 
            game_serializer.save() 
            return JSONResponse(game_serializer.data) 
        return JSONResponse(game_serializer.errors,
        status=status.HTTP_400_BAD_REQUEST) 
 
    elif request.method == 'DELETE': 
        game.delete() 
        return HttpResponse(status=status.HTTP_204_NO_CONTENT) 

The JSONResponse class is a subclass of the django.http.HttpResponse class. The superclass represents an HTTP response with a string as content. The JSONResponse class renders its content into JSON. The class defines just declare the __init__ method that created a rest_framework.renderers.JSONRenderer instance and calls its render method to render the received data into JSON save the returned bytestring in the content local variable. Then, the code adds the 'content_type' key to the response header with 'application/json' as its value. Finally, the code calls the initializer for the base class with the JSON bytestring and the key-value pair added to the header. This way, the class represents a JSON response that we use in the two functions to easily return a JSON response.

The code uses the @csrf_exempt decorator in the two functions to ensure that the view sets a Cross-Site Request Forgery (CSRF) cookie. We do this to make it simple to test this example that doesn't represent a production-ready Web Service. We will add security features to our RESTful API later.

When the Django server receives an HTTP request, Django creates an HttpRequest instance, specifically a django.http.HttpRequest object. This instance contains metadata about the request, including the HTTP verb. The method attribute provides a string representing the HTTP verb or method used in the request.

When Django loads the appropriate view that will process the requests, it passes the HttpRequest instance as the first argument to the view function. The view function has to return an HttpResponse instance, specifically a django.http.HttpResponse instance.

The game_list function lists all the games or creates a new game. The function receives an HttpRequest instance in the request argument. The function is capable of processing two HTTP verbs: GET and POST. The code checks the value of the request.method attribute to determine the code to be executed based on the HTTP verb. If the HTTP verb is GET, the expression request.method == 'GET' will evaluate to True and the code has to list all the games. The code will retrieve all the Game objects from the database, use the GameSerializer to serialize all of them, and return a JSONResponse instance built with the data generated by the GameSerializer. The code creates the GameSerializer instance with the many=True argument to specify that multiple instances have to be serialized and not just one. Under the hoods, Django uses a ListSerializer when the many argument value is set to True.

If the HTTP verb is POST, the code has to create a new game based on the JSON data that is included in the HTTP request. First, the code uses a JSONParser instance and calls its parse method with request as an argument to parse the game data provided as JSON data in the request and saves the results in the game_data local variable. Then, the code creates a GameSerializer instance with the previously retrieved data and calls the is_valid method to determine whether the Game instance is valid or not. If the instance is valid, the code calls the save method to persist the instance in the database and returns a JSONResponse with the saved data in its body and a status equal to status.HTTP_201_CREATED, that is, 201 Created.

Tip

Whenever we have to return a specific status different from the default 200 OK status, it is a good practice to use the module variables defined in the rest_framework.status module and to avoid using hardcoded numeric values.

The game_detail function retrieves, updates or deletes an existing game. The function receives an HttpRequest instance in the request argument and the primary key or identifier for the game to be retrieved, updated or deleted in the pk argument. The function is capable of processing three HTTP verbs: GET, PUT and DELETE. The code checks the value of the request.method attribute to determine the code to be executed based on the HTTP verb. No matter which is the HTTP verb, the function calls the Game.objects.get method with the received pk as the pk argument to retrieve a Game instance from the database based on the specified primary key or identifier, and saves it in the game local variable. In case a game with the specified primary key or identifier doesn't exist in the database, the code returns an HttpResponse with its status equal to status.HTTP_404_NOT_FOUND, that is, 404 Not Found.

If the HTTP verb is GET, the code creates a GameSerializer instance with game as an argument and returns the data for the serialized game in a JSONResponse that will include the default 200 OK status. The code returns the retrieved game serialized as JSON.

If the HTTP verb is PUT, the code has to create a new game based on the JSON data that is included in the HTTP request and use it to replace an existing game. First, the code uses a JSONParser instance and calls its parse method with request as an argument to parse the game data provided as JSON data in the request and saves the results in the game_data local variable. Then, the code creates a GameSerializer instance with the Game instance previously retrieved from the database (game) and the retrieved data that will replace the existing data (game_data). Then, the code calls the is_valid method to determine whether the Game instance is valid or not. If the instance is valid, the code calls the save method to persist the instance with the replaced values in the database and returns a JSONResponse with the saved data in its body and the default 200 OK status. If the parsed data doesn't generate a valid Game instance, the code returns a JSONResponse with a status equal to status.HTTP_400_BAD_REQUEST, that is, 400 Bad Request.

If the HTTP verb is DELETE, the code calls the delete method for the Game instance previously retrieved from the database (game). The call to the delete method erases the underlying row in the games_game table, and therefore, the game won't be available anymore. Then, the code returns a JSONResponse with a status equal to status.HTTP_204_NO_CONTENT that is, 204 No Content.

Now, we have to create a new Python file named urls.py in the games folder, specifically, the games/urls.py file. The following lines show the code for this file that defines the URL patterns that specifies the regular expressions that have to be matched in the request to run a specific function defines in the views.py file. The code file for the sample is included in the restful_python_chapter_01_01 folder:

from django.conf.urls import url 
from games import views 
 
urlpatterns = [ 
    url(r'^games/$', views.game_list), 
    url(r'^games/(?P<pk>[0-9]+)/$', views.game_detail), 
] 

The urlpatterns list makes it possible to route URLs to views. The code calls the django.conf.urls.url function with the regular expression that has to be matched and the view function defined in the views module as arguments to create a RegexURLPattern instance for each entry in the urlpatterns list.

We have to replace the code in the urls.py file in the gamesapi folder, specifically, the gamesapi/urls.py file. The file defines the root URL configurations, and therefore, we must include the URL patterns declared in the previously coded games/urls.py file. The following lines show the new code for the gamesapi/urls.py file. The code file for the sample is included in the restful_python_chapter_01_01 folder:

from django.conf.urls import url, include 
 
urlpatterns = [ 
    url(r'^', include('games.urls')), 
] 

Now, we can launch Django's development server to compose and send HTTP requests to our unsecure Web API (we will definitely add security later). Execute the following command:

python manage.py runserver

The following lines show the output after we execute the preceding command. The development server is listening at port 8000 .

Performing system checks...
System check identified no issues (0 silenced).
May 20, 2016 - 04:22:38
Django version 1.9.6, using settings 'gamesapi.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

With the preceding command, we will start Django development server and we will only be able to access it in our development computer. The preceding command starts the development server in the default IP address, that is, 127.0.0.1 (localhost). It is not possible to access this IP address from other computers or devices connected on our LAN. Thus, if we want to make HTTP requests to our API from other computers or devices connected to our LAN, we should use the development computer IP address, 0.0.0.0 (for IPv4 configurations), or :: (for IPv6 configurations) as the desired IP address for our development server.

If we specify 0.0.0.0 as the desired IP address for IPv4 configurations, the development server will listen on every interface on port 8000. When we specify :: for IPv6 configurations, it will have the same effect. In addition, it is necessary to open the default port 8000 in our firewalls (software and/or hardware) and configure port-forwarding to the computer that is running the development server. The following command launches Django's development server in an IPv4 configuration and allows requests to be made from other computers and devices connected to our LAN:

python manage.py runserver 0.0.0.0:8000

Tip

If you decide to compose and send HTTP requests from other computers or devices connected to the LAN, remember that you have to use the development computer's assigned IP address instead of localhost. For example, if the computer's assigned IPv4 IP address is 192.168.1.106, instead of localhost:8000, you should use 192.168.1.106:8000. Of course, you can also use the host name instead of the IP address. The previously explained configurations are very important because mobile devices might be the consumers of our RESTful APIs and we will always want to test the apps that make use of our APIs in our development environments.