This is part of a series of posts on learning Python and building a Python web service.
Choice of Framework – Part 2
My first goal was to set up two endpoints, one for searching for an artist/album/song and one for lookup of a particular entity, to the point where I could send data in and receive some back. The next goal was to be able to deploy the service to the cloud (Azure) and have everything still work as expected. Once that was done I would begin the process of fetching and returning music metadata. For now it was time to dig into how APIFlask does things.
Python has good support for decorators, and APIFlask takes advantage of them to define the input, output, and HTTP operation for an endpoint. In this way it’s similar to libraries I’ve used with Express that augment how you define endpoints. The decorator for input lets you define the names of the API parameters, and then APIFlask will populate matching arguments to the function that will respond to the given request. You can also specify validation rules and defaults that let you avoid writing code to do any manual checking. I’m using those to set defaults for pagination parameters on the search and making sure search text is included as a query string parameter. Note that the input decorator is optional, and you can use more than one on a function. All of this is managed by defining a schema for the input. Here is the definition for the search endpoint and its schemas:
@app.get('/search/<string:entity_type>') @app.input(SearchParameters, location='query') @app.output(SearchOutput) def search(entity_type, query_data): ... class SearchParameters(Schema): page = Integer(load_default=1) pageSize = Integer(load_default=10, validate=OneOf([10, 25])) query = String(required=True, metadata={'description': 'The search text'}) class SearchOutput(Schema): rows = List(Nested(SearchResult())) count = Integer()
The tricky part there was the endpoint had a path parameter and query string parameters, and at first I couldn’t figure out exactly how to configure all of that. The path parameter is easy, the name can go in the @get
endpoint path and it will be matched with the first argument to the search
function. The @input
decorator specified the query string parameters via the name of the schema class for them. It apparently puts all the values into a single object rather than into multiple variables. I called the argument query_data
but I don’t think it matters what you name it, since it will always be the second argument to the function.
I had tried adding two @input
s but couldn’t quite get it to work. You are supposed to be able to specify the location as path
, but I couldn’t see how you would tie that to the part of the path the represents the value. The schemas are fairly straightforward class definitions and APIFlask passes them to Marshmallow under the hood. They are mostly mapped to the shape of the data that MusicBrainz returns, though they are generic enough that they should work with other providers. One twist is any class field that is itself an object needed to be wrapped in the Nested
class. In the end I was able to define everything in a way that worked and kept the framework happy.
Here are the output schemas for the lookup endpoint:
class Album(Schema): id = String() name = String() artist = String() release_date = String() description = String() tags = List(String()) image = Nested(Image()) links = List(String()) class Artist(Schema): id = String() name = String() description = String() life_span = Dict() area = Dict() begin_area = Dict() tags = List(String()) images = List(Nested(Image())) albums = List(Nested(Album())) members = List(Nested(BandMember())) links = List(String())
To test the request/response cycle I simply included the inputs in the JSON response. I tested the endpoints using the same tool I’ve been using for a while now, and that’s SoapUI (a.k.a. ReadyAPI). I have the open source edition which is not the easiest thing in the world to use, but it does the job (probably Postman would be better, but that would have been yet another thing to learn).
Final review for APIFlask: thumbs up.
Despite the documentation not having an example for my exact situation, it was decent enough to get things going. I like the ability to set things so the framework does some of the work for you. It also includes stuff like some nice built-in error handling and auto-generating Swagger UI docs.