Python REST API Client Adhering to a Swagger Schema
Posted on Sat 11 February 2017 in REST • 3 min read
After building a REST API I left it alone for a while before coming back to try and get a client working properly.
Similar goals here to back then - find a good library to use going forwards that does the heavy lifting and make a simple example project. I chose a simple microservice that provides an API for decks of playing cards - like a casino dealer type of thing - which would be a client of the datastore I created in the link above.
Choosing a Client Package
The standard seems to be to generate code to meet a schema, but I’d rather load it dynamically into an app if possible.
After a bit of looking I found Bravado which looked promising. However, it was a pain to install - it relies on the twisted package and that doesn’t seem to install on Windows through pip successfully, so after some failed attempts to get it to work I gave up. I figure it’s not worth using if it’s going to be a pain for others to install - shame :(.
Next up was pyswagger - it seems to provide all the functionality and be well maintained (commits in the past few days when I wrote this). The API doesn’t look as polished to me when compared to bravado
above, but it worked fine in practice.
On the testing point - pyswagger
uses requests
for HTTP requests, so we can easily test using the responses library to provide mock responses to them.
The First Test
This was painful to get working, but as always trivial to fix in the end.
I needed to load in the schema, and then intercept the requests over the network for each API call. Intercepting the API calls was trivial with responses
, but loading in the schema turned out to be less so.
There were two approaches I could see:
pyswagger
lets you load in a schema from file, so just pass it a file name- intercept the request for the schema over the network
However, both failed when I tried:
- if I loaded in the schema from file,
pyswagger
would then try and make API requests using thefile
scheme, which causes it to fall over as that’s not a supported scheme for that part it seems. - the request for the schema over the network uses
urllib.urlopen
rather thanrequests
so I couldn’t easily intercept it withresponses
and would have to do more mocking of the deep internals ofpyswagger
, which seemed wrong.
Lots of trial-and-error and reading of the pyswagger
source later I found the solution in the tests of the client in pyswagger
itself. They were doing exactly what I was trying in the first bullet above - loading in the schema from file then intercepting API requests - so why were their API requests working? It turns out their schema had two entries not added by Flask-RESTPlus
- host
and schemes
.
So I just needed this in top level of my schema saved in a file for the tests:
{
"host": "127.0.0.1:5000",
"schemes":[
"http"
],
...
}
With that, pyswagger
can load schema from file for tests, and then send API requests over HTTP to that address, and we can use responses
to catch those and respond to them.
Phew!
Overall
Once I had wasted far too long trying to get a test working, everything else was straightforward, basically just following the tutorials linked in the pyswagger
readme.
When you have loaded in the schema to pyswagger
, you just reference API calls by name in the schema, which Flask-RESTPlus
auto-generates from the class names you give to resources, and you can rename if you want - details of both of those bits of logic here. pyswagger
makes sure you pass in all the right arguments in the right format, so if you have models in the schema you pretty much have to use the API correctly or any attempts will be rejected, which is great for catching bugs early.
Result
Here’s the project - microcarddeck. I didn’t end up implementing much function because I didn’t need to - I got to exercise the pyswagger
package and get a feel for how to use it, and got a good simple bit of reference code to crib from in the future.