FastAPI is a neat python server framework that allows us to setup a server quickly . It automatically generates an OpenAPI spec via its decorated methods and exposes it when the server is running.
Sometimes we want to generate some kind of client directly e.g. for an Angular application in the same repo or for some other client maybe for some E2E Tests without launching the server.
This post demonstrates how we can generate a client for Angular via Gradle and the OpenAPITools generators.
This approach can be used to generate any kind of client that is supported by OpenAPITools.
The full working demo can be found here:
https://github.com/mxab/fastapi-angular-openapi-gradle-demo
Let's assume our repo structure looks like this:
.
├── fastapi-server
│ ├── build.gradle
│ ├── fastapi_server
..
│ ├── poetry.lock
│ ├── pyproject.toml
└── ng-app
│ ├── build.gradle
│ ├── angular.json
..
└── src
├── app
Exporting the OpenAPI Spec
The fastapi-server
directory contains the server code and organises its dependencies via poetry
To now export an OpenAPI JSON file we create a Gradle task inside this project that looks like this:
Inside a FastAPI application you can programmatically access the OpenAPI Spec as a dict via app.openapi()
. Let's leverage this function to generate the json spec:
ext {
// the location of the exported file
openapiJSONSpec = file("$buildDir/openapi/fastapi-server.spec.json")
}
tasks.register("exportOpenAPISpec", Exec) {
commandLine = [
"poetry",
"run",
"python",
"-c",
"""
import json
import sys
from fastapi_server.app import app
json.dump(app.openapi(),sys.stdout)
""".stripIndent()]
openapiJSONSpec.parentFile.mkdirs()//otherwise FileOutputStream throws
standardOutput = new FileOutputStream(openapiJSONSpec)
inputs.dir file("fastapi_server")
outputs.file openapiJSONSpec
}
This Exec
Tasks launches a python command via poetry so we automatically use the correct venv.
It imports the FastAPI app
retrieves the openapi dict and dumps it as json to the stdout
.
The Gradle task prints the output of the process to a file.
You can run ./gradlew fastapi-server:exportOpenAPISpec
to see the result.
But we are not completely done inside this project. That we can consume this generate json artifact from other projects inside our multi project we need to make Gradle aware of it.
Therefore we create a new configuration called openapi
that can be consumed.
configurations {
openapi {
canBeConsumed = true
canBeResolved = false
}
}
And we also tell Gradle that the generated file is an artifact of this configuration that is build by the task we registered:
artifacts {
openapi(openapiJSONSpec){
builtBy(exportOpenAPISpec)
}
}
Check the Gradle docs for more details on this.
Generate the angular client
In your angular project we want to use the OpenAPITools
generator to generate a client.
OpenAPITools Gradle plugin
We can use the official Gradle plugin for that.
plugins {
id "org.openapi.generator" version "4.3.0"
}
Retrieving the spec file as dependency
In this project we also define a configuration that is resolvable and add the fastapi-server
consumable config as dependency.
configurations {
openapi {
canBeConsumed = false
canBeResolved = true
}
}
dependencies {
openapi(project(path: ":fastapi-server", configuration: 'openapi'))
}
Dependency as input for the generator task
We can now simply tell the openApiGenerate
task that it should use the resolved file from the openapi configuration.
ext {
generator = "typescript-angular"
clientOutputDir = file("$projectDir/src/client")
}
openApiGenerate {
generatorName = generator
outputDir = clientOutputDir.path
inputSpec = configurations.openapi.singleFile.path
}
//unfortunatly the actual task does not know anything about inputs and outputs. This tweak fixes it
tasks.named("openApiGenerate"){
inputs.files configurations.openapi
outputs.dir clientOutputDir
}
When we now run
./gradlew ng-app:openApiGenerate
it first runs the export task and then the generate task. When we look into ng-app/src/client
we will see a ready made client for our backend to use.
Use in angular
In our main app.module.ts
we can now import it as a module:
import { ApiModule, BASE_PATH } from '../client';
...
import { environment } from './../environments/environment';
@NgModule({
declarations: [
AppComponent
],
imports: [
...
ApiModule
],
providers: [{
provide: BASE_PATH, useValue: environment.apiUrl
}],
bootstrap: [AppComponent]
})
export class AppModule { }
And finally we inject the DefaultService
where it's needed:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private readonly defaultService: DefaultService){
...
this.defaultService.listUsersUsersGet()
...
}
}
Conclusion
Gradle allows us to enhance our project setup easily, especially when there are multiple heterogeneous projects.
The export part could be adjusted to whatever python tooling that is used (conda, pipenv...)
The the OpenAPITools project provides many different generators which allows us to adjust this show case for different use cases.
Top comments (0)