Motivation
APIs and the developers that build them are everywhere. Even in Sporttrade, which is a pretty small company, we have multiple APIs. Each API may have one or more consumers, which we will call clients, and each API is also under active developement.
Let’s be frank, new features come in all of the time which might update or change an endpoint’s interface, add a new endpoint, or even deprecate an old interface. Well that’s a lot of changes, do we need to specifically tell the consumers of these endpoints that they have to go change their code to accomodate these changes? Well, yes, that would be nice, but there’s got to be an easy way to automate some of this to make their lives easier.
Let’s think through a scenario. Let’s say we have an iOS engineer developing an app to house some customer data. It gets and updates customer data using a REST API. How can we, as DevOps professionals, allow the iOS engineer to focus on the business logic of the app instead of maintaining the REST client code each time the API changes (which might be very often)? Enter auto-generated client libraries!
Background
Client libraries are collections of code that are specific to some API. By packaging all of that together into an easily digestible package, the developers can use them as if they are a black box and they don’t have to worry about the details of:
- Sending Web Requests
- Parsing Responses
- Maintaining Custom Exceptions
- etc.
Luckily, we can auto-generate these libraries relatively easily with two things:
- An OpenAPI Spec
- Some Tool To Write The Code From The API Spec
So, what’s an OpenAPI Spec? Well, OpenAPI documents a standard format, policies, and more for how to document your API. Typically, the spec for your API will be in either JSON or Yaml format, similar to the JSON below:
Wow! That looks confusing! Where did it come from? Who built it? Well, that actually came from the API we are going to build together. And don’t worry, I didnt manually write a single piece of that. We are going to use FastAPI and python to build that together in the next section.
But first, we still have to find a tool that will parse the OpenAPI spec and spit out a usable client library. Enter the OpenAPI Generator! This is a tool which ingests your spec and spits out a library for a variety of different languages. Some languages include Python, Java, Go, Ruby, and the list goes on.
Installing the tool is very easy:
There are a few other options available in their README.md.
Writing the API
So, let’s write a simple API using python and FastAPI. This is going to be an extremely simple API since I don’t want to focus too much on the python. First, let’s install some dependencies. We will need fastapi and uvicorn, which we can install with pip
:
pip install fastapi uvicorn
Now, we are ready to rock. Our entire API is going to be less than 30 lines of code:
So, what is this doing? Let’s break it down:
This gives us one request body and one response body, both have the same format. If we were to JSONify these, they would both look like this:
{ "ping": "some-string", "pong": "some-string" }
These will be used for serialization by our API.
Then we instantiate a FastAPI server and finally, we just make some simple endpoints. All of them are at /ping
, and there is a GET
, PUT
, POST
, DELETE
at this /ping
endpoint:
That’s it for the API! Let’s recap. We made a server application, but we didn’t write a single line of client library code. Let’s let someone else write the client library for us…
Generating the Client Library
We we need to first run the server before generating the client library. So let’s run our API:
cd api
poetry run uvicorn app:app --reload
To make sure everything is up and running, let’s open a browser and navigate to a few pages:
They should look something like the below:
As previously noted, we are going to use the OpenAPI Generator to generate our client libraries for us. It’s free, easy to install, and easy to use. Refer to the section above for installation instructions or refer to their installations page.
You can do a quick test by running openapi-generator
on your command line:
Now, let’s generate this client library!
The openapi generator takes a few things as input to its cli:
- an input URL. This is the URL to the openAPI json or yaml file. It can also be a path on disk.
- a generator language (python/java/etc.). For a full list of supported generators see these docs.
- an output directory to store the generated client library.
So the full command might look like:
openapi-generator generate \
-i http://127.0.0.1:8000/openapi.json \
-g python \
-o ./generated
So, let’s give this a run:
And BAM! A client library has been generated for your api and it was written to the generated
directory.
A quick note — The openapi-generator is a java jar file, so the _JAVA_OPTIONS
flag makes a few JVM settings for the python generator per this issue.
Before we use the library with python, we do need to install it. We can directly pip
install that generated library from disk:
And we are ready to use our client library!
Let’s open up a new python file some-client/clientapp.py
and write a few lines of python!
First, we have to import a few items from our library:
openapi_client
- Our entire librarydefault_api
- Our default API objects which wrapes the gets/posts/puts/etc.HTTPValidationError
- A validation error custom generated for our APIPingRequest
- The ping request object we created with our serverPingResponse
- The ping response object we created with our server
Then we can configure our client to point it to our server and then finally, send a request:
We start by opening an API client connection, generating a ping request object matching the schema our server expects, and then sending a ping DELETE request.
Let’s do a quick test run:
prompt> python ./some-client/clientapp.py
DynamicSchema({'ping': 'ping', 'pong': 'pong'})
And lets check our logs:
prompy> uvicorn app:app --reload
INFO: Will watch for changes in these directories: ['/Users/alexanderfoley/mycode/openapi-client-libraries/api']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [76994] using StatReload
INFO: Started server process [77115]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: 127.0.0.1:51527 - "DELETE /ping HTTP/1.1" 200 OK <------ WOOHOO
And that’s all there is to it!
All code can be found in this public GitHub Repo.