Ir para o conteúdo

Gerando SDKs

Como o FastAPI é baseado na especificação OpenAPI, suas APIs podem ser descritas em um formato padrão que muitas ferramentas entendem.

Isso facilita gerar documentação atualizada, bibliotecas clientes (SDKs) em várias linguagens e testes ou fluxos de trabalho de automação que permanecem em sincronia com o seu código.

Neste guia, você aprenderá como gerar um SDK em TypeScript para o seu backend FastAPI.

Geradores de SDK de código aberto

Uma opção versátil é o OpenAPI Generator, que suporta muitas linguagens de programação e pode gerar SDKs a partir da sua especificação OpenAPI.

Para clientes TypeScript, o Hey API é uma solução feita sob medida, oferecendo uma experiência otimizada para o ecossistema TypeScript.

Você pode descobrir mais geradores de SDK em OpenAPI.Tools.

Dica

O FastAPI gera automaticamente especificações OpenAPI 3.1, então qualquer ferramenta que você usar deve suportar essa versão.

Geradores de SDK dos patrocinadores do FastAPI

Esta seção destaca soluções financiadas por investimento e com suporte de empresas que patrocinam o FastAPI. Esses produtos fornecem funcionalidades adicionais e integrações além de SDKs gerados com alta qualidade.

Ao ✨ patrocinar o FastAPI ✨, essas empresas ajudam a garantir que o framework e seu ecossistema continuem saudáveis e sustentáveis.

O patrocínio também demonstra um forte compromisso com a comunidade FastAPI (você), mostrando que elas se importam não apenas em oferecer um ótimo serviço, mas também em apoiar um framework robusto e próspero, o FastAPI. 🙇

Por exemplo, você pode querer experimentar:

Algumas dessas soluções também podem ser open source ou oferecer planos gratuitos, para que você possa testá-las sem compromisso financeiro. Outros geradores comerciais de SDK estão disponíveis e podem ser encontrados online. 🤓

Crie um SDK em TypeScript

Vamos começar com uma aplicação FastAPI simples:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float


class ResponseMessage(BaseModel):
    message: str


@app.post("/items/", response_model=ResponseMessage)
async def create_item(item: Item):
    return {"message": "item received"}


@app.get("/items/", response_model=list[Item])
async def get_items():
    return [
        {"name": "Plumbus", "price": 3},
        {"name": "Portal Gun", "price": 9001},
    ]
🤓 Other versions and variants
from typing import List

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float


class ResponseMessage(BaseModel):
    message: str


@app.post("/items/", response_model=ResponseMessage)
async def create_item(item: Item):
    return {"message": "item received"}


@app.get("/items/", response_model=List[Item])
async def get_items():
    return [
        {"name": "Plumbus", "price": 3},
        {"name": "Portal Gun", "price": 9001},
    ]

Note que as operações de rota definem os modelos que usam para o corpo da requisição e o corpo da resposta, usando os modelos Item e ResponseMessage.

Documentação da API

Se você for para /docs, verá que ela tem os schemas para os dados a serem enviados nas requisições e recebidos nas respostas:

Você pode ver esses schemas porque eles foram declarados com os modelos no app.

Essas informações estão disponíveis no schema OpenAPI do app e são mostradas na documentação da API.

E essas mesmas informações dos modelos que estão incluídas no OpenAPI são o que pode ser usado para gerar o código do cliente.

Hey API

Depois que tivermos uma aplicação FastAPI com os modelos, podemos usar o Hey API para gerar um cliente TypeScript. A forma mais rápida é via npx.

npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client

Isso gerará um SDK TypeScript em ./src/client.

Você pode aprender como instalar @hey-api/openapi-ts e ler sobre o resultado gerado no site deles.

Usando o SDK

Agora você pode importar e usar o código do cliente. Poderia ser assim, observe que você obtém preenchimento automático para os métodos:

Você também obterá preenchimento automático para o corpo a ser enviado:

Dica

Observe o preenchimento automático para name e price, que foi definido na aplicação FastAPI, no modelo Item.

Você terá erros em linha para os dados que você envia:

O objeto de resposta também terá preenchimento automático:

Aplicação FastAPI com Tags

Em muitos casos, sua aplicação FastAPI será maior, e você provavelmente usará tags para separar diferentes grupos de operações de rota.

Por exemplo, você poderia ter uma seção para items e outra seção para users, e elas poderiam ser separadas por tags:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float


class ResponseMessage(BaseModel):
    message: str


class User(BaseModel):
    username: str
    email: str


@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
    return {"message": "Item received"}


@app.get("/items/", response_model=list[Item], tags=["items"])
async def get_items():
    return [
        {"name": "Plumbus", "price": 3},
        {"name": "Portal Gun", "price": 9001},
    ]


@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
    return {"message": "User received"}
🤓 Other versions and variants
from typing import List

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    price: float


class ResponseMessage(BaseModel):
    message: str


class User(BaseModel):
    username: str
    email: str


@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
    return {"message": "Item received"}


@app.get("/items/", response_model=List[Item], tags=["items"])
async def get_items():
    return [
        {"name": "Plumbus", "price": 3},
        {"name": "Portal Gun", "price": 9001},
    ]


@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
    return {"message": "User received"}

Gere um cliente TypeScript com Tags

Se você gerar um cliente para uma aplicação FastAPI usando tags, normalmente também separará o código do cliente com base nas tags.

Dessa forma, você poderá ter as coisas ordenadas e agrupadas corretamente para o código do cliente:

Nesse caso você tem:

  • ItemsService
  • UsersService

Nomes dos métodos do cliente

Agora os nomes dos métodos gerados como createItemItemsPost não parecem muito “limpos”:

ItemsService.createItemItemsPost({name: "Plumbus", price: 5})

...isso ocorre porque o gerador de clientes usa o ID de operação interno do OpenAPI para cada operação de rota.

O OpenAPI exige que cada ID de operação seja único em todas as operações de rota, então o FastAPI usa o nome da função, o path e o método/operação HTTP para gerar esse ID de operação, porque dessa forma ele pode garantir que os IDs de operação sejam únicos.

Mas eu vou te mostrar como melhorar isso a seguir. 🤓

IDs de operação personalizados e nomes de métodos melhores

Você pode modificar a maneira como esses IDs de operação são gerados para torná-los mais simples e ter nomes de método mais simples nos clientes.

Neste caso, você terá que garantir que cada ID de operação seja único de alguma outra maneira.

Por exemplo, você poderia garantir que cada operação de rota tenha uma tag, e então gerar o ID de operação com base na tag e no nome da operação de rota (o nome da função).

Função personalizada para gerar IDs exclusivos

O FastAPI usa um ID exclusivo para cada operação de rota, ele é usado para o ID de operação e também para os nomes de quaisquer modelos personalizados necessários, para requisições ou respostas.

Você pode personalizar essa função. Ela recebe uma APIRoute e retorna uma string.

Por exemplo, aqui está usando a primeira tag (você provavelmente terá apenas uma tag) e o nome da operação de rota (o nome da função).

Você pode então passar essa função personalizada para o FastAPI como o parâmetro generate_unique_id_function:

from fastapi import FastAPI
from fastapi.routing import APIRoute
from pydantic import BaseModel


def custom_generate_unique_id(route: APIRoute):
    return f"{route.tags[0]}-{route.name}"


app = FastAPI(generate_unique_id_function=custom_generate_unique_id)


class Item(BaseModel):
    name: str
    price: float


class ResponseMessage(BaseModel):
    message: str


class User(BaseModel):
    username: str
    email: str


@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
    return {"message": "Item received"}


@app.get("/items/", response_model=list[Item], tags=["items"])
async def get_items():
    return [
        {"name": "Plumbus", "price": 3},
        {"name": "Portal Gun", "price": 9001},
    ]


@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
    return {"message": "User received"}
🤓 Other versions and variants
from typing import List

from fastapi import FastAPI
from fastapi.routing import APIRoute
from pydantic import BaseModel


def custom_generate_unique_id(route: APIRoute):
    return f"{route.tags[0]}-{route.name}"


app = FastAPI(generate_unique_id_function=custom_generate_unique_id)


class Item(BaseModel):
    name: str
    price: float


class ResponseMessage(BaseModel):
    message: str


class User(BaseModel):
    username: str
    email: str


@app.post("/items/", response_model=ResponseMessage, tags=["items"])
async def create_item(item: Item):
    return {"message": "Item received"}


@app.get("/items/", response_model=List[Item], tags=["items"])
async def get_items():
    return [
        {"name": "Plumbus", "price": 3},
        {"name": "Portal Gun", "price": 9001},
    ]


@app.post("/users/", response_model=ResponseMessage, tags=["users"])
async def create_user(user: User):
    return {"message": "User received"}

Gere um cliente TypeScript com IDs de operação personalizados

Agora, se você gerar o cliente novamente, verá que ele tem os nomes dos métodos melhorados:

Como você pode ver, os nomes dos métodos agora têm a tag e, em seguida, o nome da função. Agora eles não incluem informações do path da URL e da operação HTTP.

Pré-processar a especificação OpenAPI para o gerador de clientes

O código gerado ainda tem algumas informações duplicadas.

Nós já sabemos que esse método está relacionado aos items porque essa palavra está no ItemsService (retirada da tag), mas ainda temos o nome da tag prefixado no nome do método também. 😕

Provavelmente ainda queremos mantê-lo para o OpenAPI em geral, pois isso garantirá que os IDs de operação sejam únicos.

Mas para o cliente gerado, poderíamos modificar os IDs de operação do OpenAPI logo antes de gerar os clientes, apenas para tornar esses nomes de método mais agradáveis e limpos.

Poderíamos baixar o JSON do OpenAPI para um arquivo openapi.json e então poderíamos remover essa tag prefixada com um script como este:

import json
from pathlib import Path

file_path = Path("./openapi.json")
openapi_content = json.loads(file_path.read_text())

for path_data in openapi_content["paths"].values():
    for operation in path_data.values():
        tag = operation["tags"][0]
        operation_id = operation["operationId"]
        to_remove = f"{tag}-"
        new_operation_id = operation_id[len(to_remove) :]
        operation["operationId"] = new_operation_id

file_path.write_text(json.dumps(openapi_content))
import * as fs from 'fs'

async function modifyOpenAPIFile(filePath) {
  try {
    const data = await fs.promises.readFile(filePath)
    const openapiContent = JSON.parse(data)

    const paths = openapiContent.paths
    for (const pathKey of Object.keys(paths)) {
      const pathData = paths[pathKey]
      for (const method of Object.keys(pathData)) {
        const operation = pathData[method]
        if (operation.tags && operation.tags.length > 0) {
          const tag = operation.tags[0]
          const operationId = operation.operationId
          const toRemove = `${tag}-`
          if (operationId.startsWith(toRemove)) {
            const newOperationId = operationId.substring(toRemove.length)
            operation.operationId = newOperationId
          }
        }
      }
    }

    await fs.promises.writeFile(
      filePath,
      JSON.stringify(openapiContent, null, 2),
    )
    console.log('File successfully modified')
  } catch (err) {
    console.error('Error:', err)
  }
}

const filePath = './openapi.json'
modifyOpenAPIFile(filePath)

Com isso, os IDs de operação seriam renomeados de coisas como items-get_items para apenas get_items, dessa forma o gerador de clientes pode gerar nomes de métodos mais simples.

Gere um cliente TypeScript com o OpenAPI pré-processado

Como o resultado final está agora em um arquivo openapi.json, você precisa atualizar o local de entrada:

npx @hey-api/openapi-ts -i ./openapi.json -o src/client

Depois de gerar o novo cliente, você terá agora nomes de métodos “limpos”, com todo o preenchimento automático, erros em linha, etc:

Benefícios

Ao usar os clientes gerados automaticamente, você terá preenchimento automático para:

  • Métodos.
  • Corpos de requisições, parâmetros de query, etc.
  • Corpos de respostas.

Você também terá erros em linha para tudo.

E sempre que você atualizar o código do backend e regenerar o frontend, ele terá quaisquer novas operações de rota disponíveis como métodos, as antigas removidas, e qualquer outra alteração será refletida no código gerado. 🤓

Isso também significa que, se algo mudou, será refletido no código do cliente automaticamente. E se você construir o cliente, ele falhará caso haja qualquer incompatibilidade nos dados usados.

Assim, você detectará muitos erros muito cedo no ciclo de desenvolvimento, em vez de ter que esperar que os erros apareçam para seus usuários finais em produção e então tentar depurar onde está o problema. ✨