The main building blocks for a RESTful API in tornado are subclasses of the tornado.web.RequestHandler
class, that is, the base class for HTTP request handlers in Tornado. We just need to create a subclass of this class and declare the methods for each supported HTTP verb. We have to override the methods to handle HTTP requests. Then, we have to map the URL patterns to each subclass of tornado.web.RequestHandler
in the tornado.web.Application
instance that represents the Tornado Web application.
First, we will create a HexacopterHandler
class that we will use to handle requests for the hexacopter resource. Create a new api.py
file. The following lines show all the necessary imports for the classes that we will create and the code that declares the HexacopterHandler
class in the drone.py
file. Enter the next lines in the new api.py
file. The code file for the sample is included in the restful_python_chapter_09_01
folder:
import status from datetime import date from tornado import web, escape, ioloop, httpclient, gen from drone import Altimeter, Drone, Hexacopter, LightEmittingDiode drone = Drone() class HexacopterHandler(web.RequestHandler): SUPPORTED_METHODS = ("GET", "PATCH") HEXACOPTER_ID = 1 def get(self, id): if int(id) is not self.__class__.HEXACOPTER_ID: self.set_status(status.HTTP_404_NOT_FOUND) return print("I've started retrieving hexacopter's status") hexacopter_status = drone.hexacopter.get_hexacopter_status() print("I've finished retrieving hexacopter's status") response = { 'speed': hexacopter_status.motor_speed, 'turned_on': hexacopter_status.turned_on, } self.set_status(status.HTTP_200_OK) self.write(response) def patch(self, id): if int(id) is not self.__class__.HEXACOPTER_ID: self.set_status(status.HTTP_404_NOT_FOUND) return request_data = escape.json_decode(self.request.body) if ('motor_speed' not in request_data.keys()) or (request_data['motor_speed'] is None): self.set_status(status.HTTP_400_BAD_REQUEST) return try: motor_speed = int(request_data['motor_speed']) print("I've started setting the hexacopter's motor speed") hexacopter_status = drone.hexacopter.set_motor_speed(motor_speed) print("I've finished setting the hexacopter's motor speed") response = { 'speed': hexacopter_status.motor_speed, 'turned_on': hexacopter_status.turned_on, } self.set_status(status.HTTP_200_OK) self.write(response) except ValueError as e: print("I've failed setting the hexacopter's motor speed") self.set_status(status.HTTP_400_BAD_REQUEST) response = { 'error': e.args[0] } self.write(response)
The HexacopterHandler
class is a subclass of tornado.web.RequestHandler
and declares the following two methods that will be called when the HTTP method with the same name arrives as a request on this HTTP handler:
get
: This method receives the id
of the hexacopter whose status has to be retrieved in the id
argument. If the received id doesn't match the value of the HEXACOPTER_ID
class attribute, the code calls the self.set_status
method with status.HTTP_404_NOT_FOUND
as an argument to set the status code for the response to HTTP 404 Not Found
. Otherwise, the code prints a message indicating that it started retrieving the hexacopter's status and calls the drone.hexacopter.get_hexacopter_status
method with a synchronous execution and saves the result in the hexacopter_status
variable. Then, the code writes a message indicating it finished retrieving the status and generates a response
dictionary with the 'speed'
and 'turned_on'
keys and their values. Finally, the code calls the self.set_status
method with status.HTTP_200_OK
as an argument to set the status code for the response to HTTP 200 OK
and calls the self.write
method with the response
dictionary as an argument. Because response
is a dictionary, Tornado automatically writes the chunk as JSON and sets the value of the Content-Type
header to application/json
.patch
: This method receives the id
of the hexacopter that has to be updated or patched in the id
argument. As it happened in the previously explained get method, the code returns an HTTP 404 Not Found
in case the received id doesn't match the value of the HEXACOPTER_ID
class attribute. Otherwise, the code calls the tornado.escape.json_decode
method with self.request.body
as an argument to generate Python objects for the JSON string of the request body and saves the generated dictionary in the request_data
variable. If the dictionary doesn't include a key named 'motor_speed'
, the code returns an HTTP 400 Bad Request
status code. In case there is a key, the code prints a message indicating that it started setting the hexacopter's speed, calls the drone.hexacopter.set_motor_speed
method with a synchronous execution and saves the result in the hexacopter_status
variable. If the value specified for the motor speed is not valid, a ValueError
exception will be caught and the code will return an HTTP 400 Bad Request status code and the validation error messages as the response body. Otherwise, the code writes a message indicating it finished setting the motor speed and generates a response
dictionary with the 'speed'
and 'turned_on'
keys and their values. Finally, the code calls the self.set_status
method with status.HTTP_200_OK
as an argument to set the status code for the response to HTTP 200 OK and calls the self.write
method with the response
dictionary as an argument. Since response
is a dictionary, Tornado automatically writes the chunk as JSON and sets the value of the Content-Type
header to application/json
.The class overrides the SUPPORTED_METHODS
class variable with a tuple that indicates the class just supports the GET
and PATCH
methods. This way, in case the handler is requested a method that isn't included in the SUPPORTED_METHODS
tuple, the server will automatically return a 405 Method Not Allowed
status code.
Now, we will create a LedHandler
class that we will use to represent the LED resources. Open the previously created api.py
file and add the following lines. The code file for the sample is included in the restful_python_chapter_09_01
folder:
class LedHandler(web.RequestHandler): SUPPORTED_METHODS = ("GET", "PATCH") def get(self, id): int_id = int(id) if int_id not in drone.leds.keys(): self.set_status(status.HTTP_404_NOT_FOUND) return led = drone.leds[int_id] print("I've started retrieving {0}'s status".format(led.description)) brightness_level = led.get_brightness_level() print("I've finished retrieving {0}'s status".format(led.description)) response = { 'id': led.identifier, 'description': led.description, 'brightness_level': brightness_level } self.set_status(status.HTTP_200_OK) self.write(response) def patch(self, id): int_id = int(id) if int_id not in drone.leds.keys(): self.set_status(status.HTTP_404_NOT_FOUND) return led = drone.leds[int_id] request_data = escape.json_decode(self.request.body) if ('brightness_level' not in request_data.keys()) or (request_data['brightness_level'] is None): self.set_status(status.HTTP_400_BAD_REQUEST) return try: brightness_level = int(request_data['brightness_level']) print("I've started setting the {0}'s brightness level".format(led.description)) led.set_brightness_level(brightness_level) print("I've finished setting the {0}'s brightness level".format(led.description)) response = { 'id': led.identifier, 'description': led.description, 'brightness_level': brightness_level } self.set_status(status.HTTP_200_OK) self.write(response) except ValueError as e: print("I've failed setting the {0}'s brightness level".format(led.description)) self.set_status(status.HTTP_400_BAD_REQUEST) response = { 'error': e.args[0] } self.write(response)
The LedHandler
class is a subclass of tornado.web.RequestHandler
. The class overrides the SUPPORTED_METHODS
class variable with a tuple that indicates the class just supports the GET
and PATCH
methods. In addition, the class declares the following two methods that will be called when the HTTP method with the same name arrives as a request on this HTTP handler:
get
: This method receives the id
of the LED whose status has to be retrieved in the id
argument. If the received id isn't one of the keys of the drone.leds
dictionary, the code calls the self.set_status
method with status.HTTP_404_NOT_FOUND
as an argument to set the status code for the response to HTTP 404 Not Found
. Otherwise, the code retrieves the value associated with the key whose value matches the id in the drone.leds
dictionary and saves the retrieved LightEmittingDiode
instance in the led
variable. The code prints a message indicating that it started retrieving the LED's brightness level, calls the led.get_brightness_level
method with a synchronous execution, and saves the result in the brightness_level
variable. Then, the code writes a message indicating that it finished retrieving the brightness level and generates a response
dictionary with the 'id'
, 'description'
, and 'brightness_level'
keys and their values. Finally, the code calls the self.set_status
method with status.HTTP_200_OK
as an argument to set the status code for the response to HTTP 200 OK and calls the self.write
method with the response
dictionary as an argument. Since response
is a dictionary, Tornado automatically writes the chunk as JSON and sets the value of the Content-Type
header to application/json
.patch
: This method receives the id of the LED that has to be updated or patched in the id
argument. As happened in the previously explained get method, the code returns an HTTP 404 Not Found
in case the received id doesn't match the any of the keys of the drone.leds
dictionary. Otherwise, the code calls the tornado.escape.json_decode
method with self.request.body
as an argument to generate Python objects for the JSON string of the request body and saves the generated dictionary in the request_data
variable. If the dictionary doesn't include a key named 'brightness_level'
, the code returns an HTTP 400 Bad Request
status code. In case there is a key, the code prints a message indicating that it started setting the LED's brightness level, including the description for the LED, calls the drone.hexacopter.set_brightness_level
method with a synchronous execution. If the value specified for the brightness_level
is not valid, a ValueError
exception will be caught and the code will return an HTTP 400 Bad Request
status code and the validation error messages as the response body. Otherwise, the code writes a message indicating it finished setting the LED's brightness value and generates a response
dictionary with the 'id'
, 'description'
, and 'brightness_level'
keys and their values. Finally, the code calls the self.set_status
method with status.HTTP_200_OK
as an argument to set the status code for the response to HTTP 200 OK and calls the self.write
method with the response
dictionary as an argument. Since response
is a dictionary, Tornado automatically writes the chunk as JSON and sets the value of the Content-Type
header to application/json
.Now, we will create an AltimeterHandler
class that we will use to represent the altimeter resource. Open the previously created api.py
file and add the following lines. The code file for the sample is included in the restful_python_chapter_09_01
folder:
class AltimeterHandler(web.RequestHandler): SUPPORTED_METHODS = ("GET") ALTIMETER_ID = 1 def get(self, id): if int(id) is not self.__class__.ALTIMETER_ID: self.set_status(status.HTTP_404_NOT_FOUND) return print("I've started retrieving the altitude") altitude = drone.altimeter.get_altitude() print("I've finished retrieving the altitude") response = { 'altitude': altitude } self.set_status(status.HTTP_200_OK) self.write(response)
The AltimeterHandler
class is a subclass of tornado.web.RequestHandler
. The class overrides the SUPPORTED_METHODS
class variable with a tuple that indicates the class just supports the GET
method. In addition, the class declares the get
method that will be called when the HTTP method with the same name arrives as a request on this HTTP handler.
The get
method receives the id
of the altimeter whose altitude has to be retrieved in the id
argument. If the received id doesn't match the value of the ALTIMETER_ID
class attribute, the code calls the self.set_status
method with status.HTTP_404_NOT_FOUND
as an argument to set the status code for the response to HTTP 404 Not Found. Otherwise, the code prints a message indicating that it started retrieving the altimeter's altitude, calls the drone.hexacopter.get_altitude
method with a synchronous execution, and saves the result in the altitude
variable. Then, the code writes a message indicating it finished retrieving the altitude and generates a response
dictionary with the 'altitude'
key and its value. Finally, the code calls the self.set_status
method with status.HTTP_200_OK
as an argument to set the status code for the response to HTTP 200 OK and calls the self.write
method with the response
dictionary as an argument. Since response
is a dictionary, Tornado automatically writes the chunk as JSON and sets the value of the Content-Type
header to application/json
.
The following table shows the method of our previously created HTTP handler classes that we want to be executed for each combination of HTTP verb and scope:
HTTP verb |
Scope |
Class and method |
|
Hexacopter |
|
|
Hexacopter |
|
|
LED |
|
|
LED |
|
|
Altimeter |
|
If the request results in the invocation of an HTTP handler class with an unsupported HTTP method, Tornado will return a response with the HTTP 405 Method Not Allowed
status code.
3.133.130.199