Generate SDK for FastAPI Application
When it comes to making API calls to your own HTTP service API, the most boring part is to write the HTTP client wrapper. If you do it in artisan way, it's difficult to replicate the behaviour like-for-like, and maintain good standards on error handling, retry, etc.
An alternative approach is to generate the SDK from the OpenAPI specification automatically. This document shows how to do it for FastAPI application.
Prerequisites
- Your application is written in FastAPI or any other HTTP framework that supports OpenAPI specification that allows you to generate the openapi.json file.
- You have docker installed on your machine or CI. In this example we will use docker to run the code generator, so that you don't have to install the JVM or nodejs dependencies.
Generate openapi.json file
This is a simple python script that will generate the openapi.json out of your FastAPI application, assuming that you have a FastAPI application called app
in the your_fastapi_app
package.
import os
import json
from your_fastapi_app import app
schema = app.openapi()
spec_dir = os.path.join("sdk", "spec", "apiserver")
api_file_path = os.path.join(spec_dir, "openapi.json")
os.makedirs(spec_dir, exist_ok=True)
with open(api_file_path, "w") as f:
json.dump(schema, f, indent=2)
Generate the SDK from the openapi.json file
These make target is all you need:
VERSION=0.1.0 # the version of the sdk
.PHONY: api-gen
api-gen: # generate the api spec
echo "Generating the api spec..."
poetry run python scripts/api-gen.py
.PHONY: python-sdk-codegen
python-sdk-codegen: api-gen # generate the python sdk into `./sdk/python`
echo "Generating the python sdk..."
sudo rm -rf sdk/python
mkdir -p sdk/python
cp .openapi-generator-ignore sdk/python/.openapi-generator-ignore
docker run --rm \
-v $(PWD)/sdk:/local/sdk \
openapitools/openapi-generator-cli:v7.10.0 generate \
-i /local/sdk/spec/apiserver/openapi.json \
--api-package api \
--model-package models \
-g python \
--package-name yourappsdk \
-o /local/sdk/python \
--additional-properties=packageVersion=$(VERSION)
sudo chown -R $(USER):$(USER) sdk
.PHONY: go-sdk-codegen
go-sdk-codegen: # generate the go sdk into `./cli/sdk`
echo "Generating the go sdk..."
sudo rm -rf cli/sdk
mkdir -p cli/sdk
cp .openapi-generator-ignore cli/sdk/.openapi-generator-ignore
docker run --rm \
-v $(PWD)/cli/sdk:/local/cli/sdk \
-v $(PWD)/sdk/spec/apiserver/openapi.json:/local/openapi.json \
openapitools/openapi-generator-cli:v7.10.0 generate \
-i /local/openapi.json \
--api-package api \
--model-package models \
-g go \
--package-name yourappsdk \
--git-user-id jingkaihe \
--git-repo-id yourapp/cli/sdk \
-o /local/cli/sdk \
--additional-properties=packageVersion=$(VERSION),withGoMod=false
sudo chown -R $(USER):$(USER) cli/sdk
Notes in the make targets examples above we use sudo
to change the ownership of the generated files to the current user. This is because the generated files are owned by root in the docker container. This is needed when you use docker running on Linux. I don't expect this to be an issue on MacOS as they are often dealt with seamlessly as a matter of DX.
We use openapitools/openapi-generator-cli:v7.10.0
as the swagger codegen tool instead of the swaggerapi/swagger-codegen-cli
as the former simply does a better job when it comes to:
- Better package management
- Better data modelling and idiomatic Go code generation
- More configuration options
- More modern tooling - e.g.
swagger-codegen-cli
doesn't even support go.mod where asopenapi-generator-cli
does.
Some boilerplate for go sdk usage
The configuration for the go sdk isn't particularly obvious, so here's some boilerplate code to get you started.
import (
"net/url"
yourapp "github.com/jingkaihe/yourapp/cli/sdk"
)
func getClient(endpoint string) (*yourapp.APIClient, error) {
cfg := yourapp.NewConfiguration()
url, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
cfg.Host = url.Host
cfg.Scheme = url.Scheme
client := yourapp.NewAPIClient(cfg)
client.GetConfig().Servers = []yourapp.ServerConfiguration{
{
URL: endpoint, // this is for dealing with endpoint with path prefix such as https://api.yourapp.com/api/
},
}
return client, nil
}
To add extra headers you can
client.GetConfig().AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", theToken))
Conclusion
In this article we have shown how to generate the SDK for a FastAPI application. Frankly the generated code is not the most cleverly implemented code you will ever see, but practically I find it saves tremendous amount of time and effort to implement the client side features.