Skip to content

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 as openapi-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.