How to generate an OpenAPI/Swagger spec with FastAPI
Many developers start their API development with FastAPI, and with good reason. FastAPI has rapidly gained traction in the Python community for its excellent performance, intuitive design, and flexibility. It enables developers to craft API solutions that not only run fast but also meet their users' unique needs.
FastAPI is great for building your core API, but you'll want to layer on SDKs and docs to provide your users with easy integration. For that, you'll want an OpenAPI file.
The good news is that FastAPI provides you with an OpenAPI file out of the box. The less good news is that you'll need some tweaking to get the OpenAPI spec to a level where it becomes usable with other tooling.
This article will show you how to improve the default OpenAPI spec generation to make the most of the generated schema.
Generating an OpenAPI Spec With FastAPI
Understanding how FastAPI generates OpenAPI schemas can help you make more informed decisions when you customize your FastAPI setup.
The process is fairly straightforward: FastAPI builds the OpenAPI schema based on the routes and models you've defined in your application. For every route in your FastAPI application, FastAPI adds an operation to the OpenAPI schema. For every model used in these routes, FastAPI adds a schema definition. The request and response bodies, parameters, and headers all draw from these schema definitions.
While this process works well out of the box, FastAPI also offers several customization options that can change the generated OpenAPI schema. We'll cover some of these options in the following sections.
Our FastAPI Example App: APItizing Burgers
Let's get this out of the way: the name came in a daydream shortly before lunchtime.
To guide us through this journey, we'll use a simple example FastAPI application: the "APItizing Burgers" burger shop API. This API includes two models, Burger
and Order
, and provides basic CRUD operations for managing burgers and orders at our hypothetical burger shop. Additionally, we have a webhook defined for order status events.
We'll look at how we optimized this FastAPI application and refined our models and routes so that the generated OpenAPI specification is intuitive and easy to use. We will also explore how we can use this schema to generate SDKs using Speakeasy. The source code for our example API is available in the apitizing-burgers (opens in a new tab) repository.
The repository consists of two directories: app
and sdk
.
The app
directory contains only our FastAPI server definition: app/main.py
. This is where we'll look at what we customized.
The sdk
directory and the two specifications, openapi.yaml
and openapi.json
, are generated by running gen.sh
in the root of the project.
Join us as we dive into FastAPI customization and discover how these tweaks can streamline your SDK generation process.
WARN
datetime
objects is not timezone-aware. This will cause a mismatch with the OpenAPI format date-time
, which requires RFC 3339 date-time strings with timezones included. Consider using AwareDatetime
(opens in a new tab) fields in Pydantic models to enable the appropriate validation (opens in a new tab) and ensure your SDK behavior matches the response definition from your server.Basic FastAPI Setup
Let's get started with the basics – some things you probably do already.
These straightforward examples are trivial but will help you better understand the three steps in the automation pipeline: How FastAPI setup influences OpenAPI specifications, which, in turn, influences SDK code.
Add a List of Servers to Your FastAPI App
This may seem obvious, but while first working with FastAPI in development, the generated docs, development server, and API operations all work out of the box without the need to manually specify your server address.
However, when generating SDKs, your OpenAPI spec needs to list servers.
In our app/main.py
, we added our local server as shown.
This leads to the following generated output in openapi.yaml
.
Add a Title, Summary, Description, and Version to Your FastAPI App
In our app/main.py
, if we have the following.
FastAPI generates the following yaml in our openapi.yaml
file.
Route-Level Customizations: Enhancing Usability
With the basics out of the way, let's look at a few more substantial recommendations.
Add Typed Additional Responses to FastAPI Routes
When developers use your generated SDK, they may wish to see what all the possible responses for an API call could be.
With FastAPI, you can add additional responses to each route by specifying a response type.
In our app/main.py
, we added this abbreviated code.
FastAPI adds a schema for our specific error message to openapi.yaml
.
Group FastAPI Operations With OpenAPI Tags and Tag Metadata
As your API develops and grows bigger, you're likely to split it into separate files. FastAPI provides conveniences (opens in a new tab) to help reduce boilerplate and repetition when splitting an API into multiple modules.
While this separation may reduce cognitive overhead while you're working in particular sections of the API code, it doesn't mean similar groups are automatically created in your documentation and SDK code.
We recommend you add tags to all operations in FastAPI, whether you're building a big application or only have a handful of operations, so that operations can be grouped by tag in generated SDK code and documentation.
Add Tags to Operations
The most straightforward way to add tags is to edit each operation and add a list of tags. This example highlights the tags list.
Add Metadata to Tags
You can add metadata to your tags to further improve the developer experience.
FastAPI accepts a parameter called openapi_tags
, which we can use to add metadata, such as a description and a list of external documentation links.
Here's how to add metadata to tags.
How Tags Are Added to the OpenAPI Spec
When we add metadata to tags, FastAPI adds a top-level tags
section to our OpenAPI spec.
Each tagged path in our OpenAPI spec also gets a list of tags.
Customize the OpenAPI operationId
Generated by FastAPI
When FastAPI outputs an OpenAPI spec, it generates a unique OpenAPI operationId
for each path. By default, this unique ID is generated by the FastAPI generate_unique_id
function.
This can often lead to cumbersome and unintuitive names. To improve usability, we have two methods of customizing these generated strings.
Option 1: Customize the FastAPI generate_unique_id_function
Function
The preferred method is to use a custom function when you generate unique IDs for paths.
The example below is an illustrative function that doesn't generate guaranteed-unique IDs and doesn't handle method names without an underscore. However, it demonstrates how you can add a function that generates IDs based on an operation's method name.
Option 2: Add an Operation ID per Operation
With FastAPI, you can specify the operationId
per operation. For our example, we'll add a new parameter called operation_id
to the operation decorator.
Add Webhooks for Real-Time Notifications
Starting with OpenAPI version 3.1.0, it is possible to specify webhooks for your application in OpenAPI.
Here's how to add a webhook to FastAPI:
FastAPI generates the following top-level webhooks
section in openapi.yaml
.
Integrating Speakeasy
Now that we have a customized OpenAPI spec, we can use Speakeasy to generate SDKs based on it. Let's take a look at how the information we detailed in the OpenAPI specification affects how Speakeasy generates SDKs.
Server Information
After we added our local server information, this is how it generates in the openapi.yaml
file.
After Speakeasy generates the SDK, this leads to the following abbreviated code in sdk/src/sdk/sdkconfiguration.py
.
You'll find calls to SDKConfiguration.get_server_details()
when the SDK builds API URLs.
Title, Summary, and Description
Speakeasy uses the title, summary, and descriptions we provided earlier to add helpful text to the generated SDK documentation, including comments in the SDK code. For example, in sdk/src/sdk/sdk.py
.
Speakeasy adds the version to the SDKConfiguration
in sdk/src/sdk/sdkconfiguration.py
.
Eventually, when your users call your API using the generated SDK, this version makes its way into the headers passed with requests. For example, in sdk/src/sdk/burger.py
, we see that headers['user-agent']
includes a reference to SDKConfiguration.openapi_doc_version
.
This way, your server logs will include the FastAPI App version along with other helpful details about the SDK.
Customizing FastAPI operation_id
The unique operation_id
generated by FastAPI does not translate well into an SDK. We need to customize the unique operation_id
that FastAPI generates for better readability.
For instance, in the operation that returns a burger by burger_id
, the default unique ID would be read_burger_burger__burger_id__get
. This makes its way into SDK code, leading to class names such as ReadBurgerBurgerBurgerIDGetRequest
or function names like read_burger_burger_burger_id_get
.
Here's a usage example after generating an SDK without customizing the operationId
.
However, after using the custom function we defined previously, the read_burger
operation gets a much friendlier operation ID: readBurger
. And the usage example becomes much easier to read.
In addition to the two methods described earlier for customizing the operation_id
, there is a third way. We can add the top-level x-speakeasy-name-override
extension to our OpenAPI spec, allowing Speakeasy to override these generated names when it generates SDK code.
To add this extension, follow the Speakeasy guide on changing method names.
Add Retries to Your SDK With x-speakeasy-retries
Speakeasy can generate SDKs that follow custom rules for retrying failed requests. For instance, if your server fails to return a response within a specified time, you may want your users to retry their request without clobbering your server.
To add retries to SDKs generated by Speakeasy, add a top-level x-speakeasy-retries
schema to your OpenAPI spec. You can also override the retry strategy per operation by adding x-speakeasy-retries
to each operation.
Adding Global Retries
To add global retries, we need to customize the schema generated by the FastAPI get_openapi
function.
Keep in mind you'll need to add this customization after declaring your operation routes.
This change adds the following top-level section to openapi.yaml
.
Adding Retries per Request
To add x-speakeasy-retries
to a single operation, update the operation and add the openapi_extra
parameter as follows.
Add a List of Servers to Your FastAPI App
This may seem obvious, but while first working with FastAPI in development, the generated docs, development server, and API operations all work out of the box without the need to manually specify your server address.
However, when generating SDKs, your OpenAPI spec needs to list servers.
In our app/main.py
, we added our local server as shown.
This leads to the following generated output in openapi.yaml
.
Add a Title, Summary, Description, and Version to Your FastAPI App
In our app/main.py
, if we have the following.
FastAPI generates the following yaml in our openapi.yaml
file.
Route-Level Customizations: Enhancing Usability
With the basics out of the way, let's look at a few more substantial recommendations.
Add Typed Additional Responses to FastAPI Routes
When developers use your generated SDK, they may wish to see what all the possible responses for an API call could be.
With FastAPI, you can add additional responses to each route by specifying a response type.
In our app/main.py
, we added this abbreviated code.
FastAPI adds a schema for our specific error message to openapi.yaml
.
Group FastAPI Operations With OpenAPI Tags and Tag Metadata
As your API develops and grows bigger, you're likely to split it into separate files. FastAPI provides conveniences (opens in a new tab) to help reduce boilerplate and repetition when splitting an API into multiple modules.
While this separation may reduce cognitive overhead while you're working in particular sections of the API code, it doesn't mean similar groups are automatically created in your documentation and SDK code.
We recommend you add tags to all operations in FastAPI, whether you're building a big application or only have a handful of operations, so that operations can be grouped by tag in generated SDK code and documentation.
Add Tags to Operations
The most straightforward way to add tags is to edit each operation and add a list of tags. This example highlights the tags list.
Add Metadata to Tags
You can add metadata to your tags to further improve the developer experience.
FastAPI accepts a parameter called openapi_tags
, which we can use to add metadata, such as a description and a list of external documentation links.
Here's how to add metadata to tags.
How Tags Are Added to the OpenAPI Spec
When we add metadata to tags, FastAPI adds a top-level tags
section to our OpenAPI spec.
Each tagged path in our OpenAPI spec also gets a list of tags.
Customize the OpenAPI operationId
Generated by FastAPI
When FastAPI outputs an OpenAPI spec, it generates a unique OpenAPI operationId
for each path. By default, this unique ID is generated by the FastAPI generate_unique_id
function.
This can often lead to cumbersome and unintuitive names. To improve usability, we have two methods of customizing these generated strings.
Option 1: Customize the FastAPI generate_unique_id_function
Function
The preferred method is to use a custom function when you generate unique IDs for paths.
The example below is an illustrative function that doesn't generate guaranteed-unique IDs and doesn't handle method names without an underscore. However, it demonstrates how you can add a function that generates IDs based on an operation's method name.
Option 2: Add an Operation ID per Operation
With FastAPI, you can specify the operationId
per operation. For our example, we'll add a new parameter called operation_id
to the operation decorator.
Add Webhooks for Real-Time Notifications
Starting with OpenAPI version 3.1.0, it is possible to specify webhooks for your application in OpenAPI.
Here's how to add a webhook to FastAPI:
FastAPI generates the following top-level webhooks
section in openapi.yaml
.
Integrating Speakeasy
Now that we have a customized OpenAPI spec, we can use Speakeasy to generate SDKs based on it. Let's take a look at how the information we detailed in the OpenAPI specification affects how Speakeasy generates SDKs.
Server Information
After we added our local server information, this is how it generates in the openapi.yaml
file.
After Speakeasy generates the SDK, this leads to the following abbreviated code in sdk/src/sdk/sdkconfiguration.py
.
You'll find calls to SDKConfiguration.get_server_details()
when the SDK builds API URLs.
Title, Summary, and Description
Speakeasy uses the title, summary, and descriptions we provided earlier to add helpful text to the generated SDK documentation, including comments in the SDK code. For example, in sdk/src/sdk/sdk.py
.
Speakeasy adds the version to the SDKConfiguration
in sdk/src/sdk/sdkconfiguration.py
.
Eventually, when your users call your API using the generated SDK, this version makes its way into the headers passed with requests. For example, in sdk/src/sdk/burger.py
, we see that headers['user-agent']
includes a reference to SDKConfiguration.openapi_doc_version
.
This way, your server logs will include the FastAPI App version along with other helpful details about the SDK.
Customizing FastAPI operation_id
The unique operation_id
generated by FastAPI does not translate well into an SDK. We need to customize the unique operation_id
that FastAPI generates for better readability.
For instance, in the operation that returns a burger by burger_id
, the default unique ID would be read_burger_burger__burger_id__get
. This makes its way into SDK code, leading to class names such as ReadBurgerBurgerBurgerIDGetRequest
or function names like read_burger_burger_burger_id_get
.
Here's a usage example after generating an SDK without customizing the operationId
.
However, after using the custom function we defined previously, the read_burger
operation gets a much friendlier operation ID: readBurger
. And the usage example becomes much easier to read.
In addition to the two methods described earlier for customizing the operation_id
, there is a third way. We can add the top-level x-speakeasy-name-override
extension to our OpenAPI spec, allowing Speakeasy to override these generated names when it generates SDK code.
To add this extension, follow the Speakeasy guide on changing method names.
Add Retries to Your SDK With x-speakeasy-retries
Speakeasy can generate SDKs that follow custom rules for retrying failed requests. For instance, if your server fails to return a response within a specified time, you may want your users to retry their request without clobbering your server.
To add retries to SDKs generated by Speakeasy, add a top-level x-speakeasy-retries
schema to your OpenAPI spec. You can also override the retry strategy per operation by adding x-speakeasy-retries
to each operation.
Adding Global Retries
To add global retries, we need to customize the schema generated by the FastAPI get_openapi
function.
Keep in mind you'll need to add this customization after declaring your operation routes.
This change adds the following top-level section to openapi.yaml
.
Summary
In this post, we've explored how you can set up a FastAPI-based SDK generation pipeline without hand-editing or updating OpenAPI specifications. By using existing FastAPI methods for extending and customizing OpenAPI specifications, you can improve the usability of your generated client SDKs.