More sophisticated routing and responses

We'll look at two additional features common to Flask applications and RESTful APIs:

  • The first is more sophisticated path parsing.
  • The second is an Open API specification document.

Flask provides several clever parsing features to decompose the path in a URI. Perhaps one of the most useful parsers is used to extract individual field items from the URL string. We can use this as shown in the following example:

@app.route("/hands/<int:h>/dominoes/<int:c>")
def hands(h: int, c: int) -> Tuple[Dict[str, Any], int]:
if h == 0 or c == 0:
return jsonify(
status="Bad Request", error=[f"hands={h!r}, dominoes={c!r} is invalid"]
), HTTPStatus.BAD_REQUEST

if app.env == "development":
random.seed(2)
b = Boneyard(limit=6)
try:
hand_list = b.deal(c, h)
except ValueError as ex:
return jsonify(
status="Bad Request", error=ex.args
), HTTPStatus.BAD_REQUEST
app.logger.info("Send %r", hand_list)

return jsonify(
status="OK",
dominoes=[[astuple(d) for d in hand] for hand in hand_list]
), HTTPStatus.OK

This uses <int:h> and <int:c> to both parse the values from the path and ensure that the values are integers. Any requests with paths that don't match this pattern will receive a 404 Not Found error status.

The four steps of handling the response are slightly more complex. The details are as follows:

  • The parsing step includes a range check on the parameter values. In the case of a 0 value, this will lead to 400 Bad Request errors. The pattern of the request fits the route's template, but the values were invalid.
  • The evaluating step includes an exception handler. If the underlying application model, the Boneyard object in this example, raises an exception, this Flask container will produce a useful, informative error message. Without this exception-handling block, ValueError would lead to an HTTP status code of 500 Internal Server Error.
  • The logging step didn't change from the previous example.
  • The responding step builds the list of individual hands by dealing tiles. This uses a more complex nested list comprehension. The nested comprehension can be read from right to left. The Python syntax is as follows:
    [[astuple(d) for d in hand] for hand in hand_list]
    If we read the code in reverse, we can see that the nested comprehension has the following structure. It processes each hand in hand_list. Given hand, it then processes each domino in hand. The processing is provided first, which represents the domino object, d, as a tuple. 

The goal of most RESTful web services development is to create an underlying application model that permits simple wrapper functions such as the following example. 

An additional feature that is central to a successful RESTful server is a specification of the contract that the server adheres to. A document that follows the OpenAPI specification is a useful way to present this contract. For more information on the Open API specification, see https://swagger.io/specification/. The document service will look as follows:

OPENAPI_SPEC = {
"openapi": "3.0.0",
"info": {
"description": "Deals simple hands of dominoes",
"version": "2019.02",
"title": "Chapter 13. Example 2",
},
"paths": {}
}

@app.route("/openapi.json")
def openapi() -> Dict[str, Any]:
return jsonify(OPENAPI_SPEC)

The specification in this example is a tiny skeleton with only a few required fields. In many applications, the specification will be kept in a static file and served separately. In very complex environments, there will be external references and the document may be provided in multiple, separate sections. To an extent, it's possible to deduce elements of the OpenAPI specification from the Flask routes themselves.

While this is technically possible, it subverts the idea of having a formal and independent contract for the features of the RESTful API. It seems better to have an external contract and check compliance against that contract.

The function that implements the route does almost nothing to transform the Python dictionary into JSON notation and respond with a document and the default status of 200 OK.

We'll see how to implement a REST client in the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset