O que é

  • FastAPI é um framework moderno e rápido para construir APIs com Python.
  • Baseado em Python 3.7+.
  • Utiliza tipagem (type hints) para validação automática.
  • Utiliza json para retornar qualquer dado de um endpoint.
  • Gera documentação interativa automaticamente.
  • Nota: É recomendado que os arquivos de estudo de APIs e protocolo HTTP sejam visitados antes de estudar o que vem a seguir.

Vantagens

  • Rápido: baseado em ASGI (uvicorn, starlette).
  • Validação automática: usando Pydantic.
  • Documentação automática: Swagger UI e Redoc.
  • Suporte a autenticação: OAuth2, JWT, etc.
  • Fácil de usar: código simples e intuitivo.

Instalação

  • Para conseguir utilizar o framework, é necessário seguir os seguintes passos:

1. Instale o módulo do fastAPI:

pip install fastapi

2. Instale uvicorn:

pip install uvicorn
  • Para descobrir se a instalação ocorreu como esperado (faça isso no seu arquivo python):
import fastapi
  • Rode o arquivo, se não ocorrerem erros, a instalação foi eficaz.

Criando uma API

Importe o módulo fastAPI e crie um “objeto” para a API:

from fastapi import fastAPI

app = FastAPI()

Para criar um endpoint:

@app.get("/")
def home():
    return{"Dados": "Nome"}
  • “get” - Método HTTP (Para mais informações sobre métodos HTTP, confira o estudo sobre protocolo HTTP)
  • “/” - Endpoint que foi criado

Rodando a API

  • Para rodar a API, abra seu terminal no diretório em que o arquivo python foi criado e digite:
uvicorn <nome_do_arquivo>:<nome_do_objeto> --reload
  • Sendo assim:
    • <nome_do_arquivo> - nome do .py (importante notar que no terminal não se deve colocar o “.py”)
    • <nome_do_objeto> - app
    • –reload - recarrega o servidor toda vez que ocorrer uma mudança no arquivo python
  • Note que, graças ao “–reload”, é possível atualizar o arquivo em tempo real.

Acessando a documentação

  • Ao realizar a etapa anterior, adicionando o endpoint “/docs” é possível acessar uma documentação da API gerada atuomaticamente.
  • É um jeito de se testar a API de uma forma mais visual

Parâmetros de endpoint

  • Vamos construir uma dicionário em python que represente um armazém de produtos de uma loja, em que cada produto é identificado por um ID:
armazem = {
        1:{
            "nome": "maca", 
            "validade": "28/10/2028",
            "preco": 4.21
        }
    }
  • Neste caso, maca tem ID 1. Vamos criar um endpoint que retorne informações de um produto, levando em consideração sem ID:
@app.get("/get-item/{item_id}")
def get_item(item_id: int):
    return armazem[item_id]
  • Para uma melhor análise, vamos olhar linha por linha:
    • @app.get("/get-item/{item_id}") - Cria um endpoint com método get, adicionando um parâmetro “item_id” que vai ser definido na função.

    • def get_item(item_id: int): - Define a função utilizada para o endpoint, em que “item_id: int” é um parâmetro definido por um “type hint”, para que assim, nada além de um tipo inteiro seja reconhecido, gerando uma mensagem de erro caso não seja.

    • return armazem[item_id] - Retorna todas as informações do produto.

  • Então, no nosso caso, se tentassemos acessar o endpoint “/get-item/1”, obteríamos:
nome:   	"maca"
validade:	"28/10/2028"
preco:	    4.21
  • Para adicionar informações mais detalhadas ao caminho do endpoint, uma função que pode ser útil é:
from fastapi import FastAPI, Path

...

@app.get("/get-item/{item_id}")
def get_item(item_id: int = Path(None, description="Insira ID do item")):
    return armazem[item_id]
  • Note que para adicionar descrição ao path é necessário adicionar um valor “padrão” (valor que será passado se nenhum valor for informado), denotado pelo parâmetro “None”, que não retorna valor.

Parâmetros de query

  • De maneira simples, é o que vem depois do “?” em um URL. Ex: “aprender3.com/home?key=’****’/msg=succeded”.
@app.get("/get-por-nome")
def get_item(nome: str = Query(None, title="Nome", description="Nome")):
    for item_id in armazem:
        if armazem[item_id].nome == nome:
            return armazem[item_id]
    return {"Dado": "Not found"}
  • Também é possível combinar parâmetros de caminho e query.
@app.get("/get-nome/{item_id}")
def get_item(item_id: int, name: str):
    for item_id in armazem:
        if armazem[item_id]["nome"] == name:
            return armazem[item_id]
    return {"Dado": "Not found"}

Outros métodos

  • Já vimos aqui, indiretamente, o método “GET”. Vamos ver também outros métodos HTTP que podem ser utilizados no framework:

POST

from pydantic import BaseModel

class Item(BaseModel):                    # Herda de BaseModel
    nome: str
    validade: str
    preco: float

...

@app.post("/cria-item/{item_id}")
def cria_item(item_id: int, item: Item):  # item pertence à classe Item
    if item_id in armazem:
        return {"Erro": "Item existente!"}
    armazem[item_id] = item               # Insere o objeto item ao dicionário, convertendo 
    return armazem[item_id]               # em json (Utiliza o BaseModel para isso)

PUT

@app.put("/update-item/{item_id}")
def update_item(item_id: int, item: Item):
    if item_id not in armazem:
        return {"Erro": "Item não existe!"}     # Impossível atualizar um item que não existe
    
    if item.nome != None:                       # Checa se nome do item não é vazio
        armazem[item_id].nome = item.nome
    if item.validade != None:                       # Checa se validade do item não é vazio
        armazem[item_id].validade = item.validade
    if item.preco != None:                       # Checa se preco do item não é vazio
        armazem[item_id].preco = item.preco
    return armazem[item_id]

DELETE

@app.delete("/delete-item")
def delete_item(item_id: int = Query(..., description="ID do item"))        # "..." significa que é opcional
    if item_id not in armazem:
        return{"Erro": "ID não cadastrado"}
    del armazem[item_id]                                                    # Simplesmente deleta do dicionário
    return{"Sucesso": "Item deletado"}

Códigos de resposta do sistema

  • Para conseguir retornar algum código, é preciso:
from fastapi import FastAPI, HTTPException, status

...
  • Agora, quando se desejar retornar um código de resposta, ao invés de utilizar um “return” para retornar dados, use “raise HTTPException”, seguido de “status_code”, como mostrado a seguir:
@app.delete("/delete-item")
def delete_item(item_id: int = Query(..., description="ID do item"))        # "..." significa que é opcional
    if item_id not in armazem:
        raise HTTPException(satus_code = status.HTTP_404_NOT_FOUND, detail="Nome nao encontrado")       # Ou utilize o código que desejar 
    del armazem[item_id]                                                    # Simplesmente deleta do dicionário
    return{"Sucesso": "Item deletado"}