Utilizando o FastAPI – parte 1

utilizando-fastapi-1_abertura

O FastAPI é um framework web para a criação de API utilizando versões do Python posteriores a 3.6. Ele é robusto, simples e fácil de programar, de boa performance e uma opção interessante para rapidamente implementar uma API. E a ideia aqui é justamente esta, construir uma API que com os métodos HTTP mais comuns, ou seja, DELETE, GET, POST e PUT, usando os recursos disponíveis neste framework.

E nesta primeira parte a criação do projeto, noções básicas do FastAPI e os primeiros métodos HTTP

Criando um ambiente

A primeira coisa a fazer, como sempre, é criar um ambiente de desenvolvimento para o projeto.

$ mkdir fastapi
$ virtualenv --python python3 py3
created virtual environment CPython3.8.2.final.0-64 in 544ms
...
$ source py3/bin/activate

Com o ambiente já ativado, instalamos as dependências (todas, inclusive o uvicorn — o servidor HTTP dele).

(py3) $ pip install fastapi uvicorn
Collecting fastapi
  Downloading fastapi-0.59.0-py3-none-any.whl (49 kB)
     |████████████████████████████████| 49 kB 174 kB/s
...
Installing collected packages: starlette, pydantic, fastapi, click,
httptools, uvloop, h11, websockets, uvicorn
Successfully installed click-7.1.2 fastapi-0.59.0 h11-0.9.0
httptools-0.1.1 pydantic-1.6.1 starlette-0.13.4 uvicorn-0.11.5
uvloop-0.14.0 websockets-8.

E agora podemos brincar um pouco… 🙂

Experimentando o FastAPI

Começando com um código de teste — mas que ao mesmo tempo tem razoável utilidade — no arquivo “main.py”:

from datetime import datetime
from typing import Dict
from fastapi import FastAPI

app = FastAPI()

@app.get("/health/")
def alive() -> Dict[str, datetime]:
    return {"timestamp": datetime.now()}

A classe FastAPI é instanciada como “app” e criada a função alive() que retorna um simples dicionário contendo a data e hora atuais do servidor. Nesta função é usada um decorador para fazê-la responder pelo método GET do HTTP e mapeando a rota “/health”.

Ah sim, a anotação de tipo, ou type hinting, no código está aí para justamente criar um hábito e o que stá sendo definido na função é que ela retorna, o “->”, um dicionário — Dict[] — cujas chav é do tipo str e o valor um objeto datetime.

O FastAPI recomenda o uso do uvicorn como servidor ASGI e como ele foi instalado basta usar…

$ uvicorn main:app
INFO: Started server process [9049]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

verificar se tudo está funcionando corretamente…

$ curl 127.0.0.1:8000/health/
{
    "timestamp":"2020-07-18T12:03:55.198896"
}

e para terminar, basta pressionar «Ctrl»+«C» no teclado.

E a parte do “razoavelmente útil” está no fato de que este sujeito pode ser utilizado para monitorar o estado do servidor.

Criando as primeiras rotas

Para criar algo mais funcional será necessário acrescentar algumas coisas ao arquivo “main.py”:

import json
...
from typing import Dict, List, Optional, Union
from fastapi import FastAPI, HTTPException, status
...
students = json.load(open("students.json", "r"))
...

Aproveitei a lista de alunos criada para a publicação sobre CSV em Python para produzir o arquivo “students.json” que, por hora, servirá como um banco de dados de mentira¹, ou seja, um “banco de dados”… 😀

Agora que existe um “banco de dados”, uma rota que permita consultá-lo:

...
@app.get("/students/")
def get_all_students() -> List[Dict[str, Union[float, int, str]]]:
    if response := students:
        return response
    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="Não existem estudantes cadastrados.",
    )

A função é bem simples, ela responde pelo método GET em “/students/”, recupera todos os estudantes — no mundo real haveria uma função aqui para fazer a consulta ao banco de dados etc… — e armazena o conteúdo em “response”.

Havendo algo este é retornado senão é gerada uma exceção para retornar o erro 404 (não encontrado) do HTTP.

A anotação de tipo desta função é que ela retorna uma lista — List[] — formada por uma lista de elementos que consistem em um dicionário com chave str e valores podendo ser float, int ou str (daí do Union[]).

Sim, este é um exemplo para Python +3.8 e utiliza o walrus operator (PEP572) pois achei que deixaria o código mais enxuto e em versões anteriores ficaria assim:

response = students
if response:
    return response

Então, testá-la…

$ curl 127.0.0.1:8000/students/
[
   {
        "id":1,
        "name":"Agatha da Costa",
        ...
    }, {
        "id":2,
        ...
    },
    ...
]

Obviamente que apenas uma rota que lista todo o conteúdo não é lá muito prática, então:

...
@app.get("/students/{student_id}/")
def get_student(
    student_id: int
) -> Dict[str, Union[float, int, str]]:
    if response := list(
        filter(lambda i: i.get("id") == student_id, students)
    )[0]:
        return response
    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail=f"Estudante de 'id={student_id}' não encontrado.",
    )

A função responde pelo método GET em “/students/”, recebendo o id do aluno em “student_id” (o tipo exigido para a função é inteiro e passado através pela anotação do tipo na definição desta, o “:int” ao lado do nome da variável).

De resto há o uso do filter() e do lambda para ajudar encontrar o registro contendo o valor com id correto…

$ curl 127.0.0.1:8000/students/2/
{
    "id":2,
    "name":"Benício das Neves",
    "address":"Rua de da Paz, 86",
    "neighbour":"Capitão Eduardo",
    "city":"Pereira de Rodrigues",
    "state":"Rio de Janeiro",
    "postal_code":"83392-182"
}

Ou então retornar o erro 404

$ curl 127.0.0.1:8000/students/11/
{
    "detail":"Estudante '11' não encontrado."
}

E para fechar mais um item do CRUD, uma forma de remover registros do “banco de dados” com a implementação de um método que implementa o DELETE do HTTP:

@app.delete("/students/{student_id}/")
def delete_student(student_id: int) -> Dict[str, bool]:
    if response := list(
        filter(lambda i: i.get("id") == student_id, students)
    )[0]:
        del students[students.index(response)]
        return {"success": True}

    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail=f"Estudante de 'id={student_id}' não encontrado.",
    )

A função é bastante parecida com a “get_student”, a diferença mesmo está na ação final que remove o estudante selecionado do “banco de dados” ao invés de enviá-lo.

Para verificar o funcionamento…

$ curl -X DELETE 127.0.0.1:8000/students/2/
{
    "success":true
}
$ curl 127.0.0.1:8000/students/2/
{
    "detail":"Estudante '2' não encontrado."
}

E verificar que o registro foi apagado com sucesso do “banco de dados” (mas na próxima execução ele volta).

(¹) Poderia ter optado por usar o SQLite mas justamente não queria ficar escrevendo consultas em SQL para depois remover do código.

Documentação da API

utilizando-fastapi-1_openapi

Até agora foram escritos quatro funções bem simples² e o FastAPI cuidou de muita coisa como mapear uma rota para cada um deles e tambpem prover a própria documentação utillizando OpenAPI. Ela está disponível em http://127.0.0.1:8000/docs e lá está detalhado como usar cada uma das rotas e até testá-las (o que acaba sendo bastante útil).

(²) A complexidade por enquanto está literalmente na simulação de banco de dados.

Finalizando esta parte…

Este é o fim da desta parte, na próxima a ideia é completar o CRUD acrescentando a inclusão e alteração, mas isto será bom falar um pouco sobre o pydantic e também sobre anotação de tipo. Ah sim, os arquivos desta parte estão disponíveis em um repositório git lá no GitHub.

Até!

Um comentário sobre “Utilizando o FastAPI – parte 1

  1. Pingback: Utilizando o FastAPI – parte 2 | giovannireisnunes

Os comentários estão desativados.