Desenvolvimento Back-end Python

Criando o primeiro CRUD com FastAPI

Neste artigo nós iremos aprender um pouco mais sobre o FastAPI durante o desenvolvimento de uma API com todas as operações de CRUD.

28 dias atrás

No artigo anterior conhecemos um pouco sobre o FastAPI e suas principais características, agora vamos ver na prática como utilizar as funcionalidades desse framework durante o desenvolvimento de uma Web API com as operações básicas de CRUD (Create, Read, Update e Delete).

Durante o desenvolvimento desse projeto nós iremos precisar acessar o banco de dados e para isso vamos utilizar um ORM que será o SQLAlchemy e como banco de dados vamos utilizar o SQLite.

Desenvolvedor Python
Formação Desenvolvedor Python
Conhecer a formação

Criando o projeto

Para que possamos desenvolver nosso projeto é necessário que você esteja com o Python devidamente instalado em seu sistema operacional, caso não saiba como realizar a instalação do Python aqui mesmo no blog da TreinaWeb temos o artigo Instalação do Python e nosso primeiro Olá Mundo que mostra o processo de instalação da linguagem nos sistemas Linux, Windows e MacOS.

Outro ponto importante é que todo o código que será produzido durante esse artigo foi feito utilizando o Python 3.10, então caso possua uma versão diferente pode se fazer necessário algumas modificações para que o código funcione na sua versão do Python.

Primeiramente vamos criar uma pasta onde ficarão os arquivos do nosso projeto, para isso abra o terminal do seu sistema operacional e crie um diretório com o comando mkdir tw-cursos, logo em seguida precisamos entrar dentro desse diretório com o comando cd tw-cursos, uma vez dentro do diretório do projeto nós vamos criar e ativar um ambiente virtual com os seguintes comandos:

  • No Linux e MacOS
python3 -m venv .venv
source .venv/bin/activate
  • No Windows:
python -m venv .venv
.\.venv\Scripts\activate

Agora que estamos com o nosso ambiente virtual ativo podemos realizar a instalação das bibliotecas necessárias utilizando o PIP.

pip install fastapi uvicorn[standard] sqlalchemy

Após a execução do comando acima teremos feito a instalação das seguintes bibliotecas:

  • FastAPI: Microframework voltada para o desenvolvimento de Web APIs;
  • Uvicorn: Web server ASGI implementado em Python, necessário para a execução do nosso projeto;
  • SQLAlchemy: ORM bastante popular dentro do ecossistema Python.

Agora nós já estamos com o nosso ambiente preparado e podemos dar prosseguimento com o desenvolvimento de nossa API.

Configurando a conexão como banco de dados

Agora vamos configurar a conexão do SQLALchemy com o banco de dados, primeiramente vamos criar um novo arquivo chamado database.py, arquivo esse que vamos colocar as configurações necessárias do SQLAlchemy para que o mesmo consiga se conectar ao banco de dados.

Neste arquivo vamos colocar o seguinte conteúdo:

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "sqlite:///db.sqlite3"

engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

No código acima nós inicialmente temos as importações necessárias vindas do SQLAlchemy, logo em seguida temos a criação da constante SQLALCHEMY_DATABASE_URL que conterá a string de conexão com o banco de dados.

Logo em seguida temos a criação da engine do SQLAlchemy, essa engine é o ponto de partida para qualquer aplicação que utilize o SQLAlchemy, é a partir dela que o SQLAlchemy conseguirá criar as conexões com o banco de dados. Para criar a engine nós utilizamos a função create_engine e passamos para essa função dois argumentos, sendo o primeiro a string de conexão com o banco e a segunda é o argumento nomeado connect_arg que é um dicionário com opções adicionais para essa configuração. Importante frisar que o argumento connect_args={"check_same_thread": False} só é necessário para quando estamos utilizando o SQLite.

Depois, nós criamos a classe SessionLocal com o auxílio da função sessionmaker, essa classe irá representar uma sessão com o nosso banco de dados.

Após a criação da classe SessionLocal nós temos a criação da classe Base com a ajuda do método declarative_base, nós iremos utilizar essa classe mais na frente para realizar a criação das classes de modelo do nosso banco de dados.

E por fim temos a criação da função get_db que será utilizada para nos disponibilizar uma instância da classe SessionLocal, ou seja, será através da função get_db que iremos criar novas sessões com o banco de dados. Essa função será utilizada mais a frente para que possamos utilizar a funcionalidade de injeção de dependências provida pelo próprio FastAPI.

Python - SQLAlchemy ORM
Curso Python - SQLAlchemy ORM
Conhecer o curso

Criando a camada de modelo

Agora que já realizamos a configuração necessária para realizarmos a conexão com o banco de dados através do SQLAlchemy, nós vamos criar a camada de modelo de nossa aplicação, essa camada conterá classes que irão representar as tabelas do banco de dados e que o SQLAlchemy irá utilizar para gerar essas tabelas de forma automática.

Vamos criar um novo arquivo chamado models.py e dentro desse arquivo iremos colocar o seguinte conteúdo:

from sqlalchemy import Column, Integer, String

from database import Base

class Curso(Base):
    __tablename__ = "cursos"

    id: int = Column(Integer, primary_key=True, index=True)
    titulo: str = Column(String(100), nullable=False)
    descricao: str = Column(String(255), nullable=False)
    carga_horaria: int = Column(Integer, nullable=False)
    qtd_exercicios: int = Column(Integer, nullable=False)

Nessa código nós inicialmente importamos as classes do SQLAlchemy que serão necessárias para a declaração do nosso model, são as classes Column utilizada para definir uma coluna de uma tabela e as classes Integer e String utilizadas para definir o tipo de dados utilizado nas colunas de uma tabela.

Logo em seguida também realizamos a importação da classe Base criada na seção anterior, classe essa a qual todas as nossas classes de modelo devem herdar.

E por fim temos a criação da classe Curso que é de fato a nossa classe de modelo que irá representar a nossa tabela no banco de dados, nessa classe nós temos o atributo __tablename__ que serve para informar ao SQLAlchemy qual o nome deve utilizado no momento da criação dessa tabela no banco de dados e também os atributos id, titulo, descricao, carga_horaria e qtd_exercicios que irão representar as colunas dessa tabela.

Veja que em cada um dos atributos que representam as colunas da tabela nós estamos utilizando o recurso de Type Hints, esse recurso serve basicamente para que possamos trabalhar com tipagem em nosso código Python, isso é bem similar ao que o TypeScript faz para o JavaScript. O uso de tipagem no Python não é obrigatório, mas é altamente recomendado durante o desenvolvimento de projetos com o FastAPI.

Criando a camada de repositório

Nesse projeto nós vamos utilizar um padrão chamado de repositório ou repository pattern, esse padrão de projeto tem como objetivo isolar as especificidades de acesso a base de dados em uma única camada, então todo o código necessário para realizar as operações de leitura e escrita no banco de dados estarão dentro dessa camada.

Então nós vamos criar um novo arquivo chamado repositories.py e dentro desse arquivo iremos ter uma classe chamada CursoRepository e essa classe terá métodos estáticos que irão realizar as operações no banco de dados com o auxílio do SQLAlchemy. Esse arquivo terá o seguinte conteúdo:

from sqlalchemy.orm import Session

from models import Curso

class CursoRepository:
    @staticmethod
    def find_all(db: Session) -> list[Curso]:
        return db.query(Curso).all()

    @staticmethod
    def save(db: Session, curso: Curso) -> Curso:
        if curso.id:
            db.merge(curso)
        else:
            db.add(curso)
        db.commit()
        return curso

    @staticmethod
    def find_by_id(db: Session, id: int) -> Curso:
        return db.query(Curso).filter(Curso.id == id).first()

    @staticmethod
    def exists_by_id(db: Session, id: int) -> bool:
        return db.query(Curso).filter(Curso.id == id).first() is not None

    @staticmethod
    def delete_by_id(db: Session, id: int) -> None:
        curso = db.query(Curso).filter(Curso.id == id).first()
        if curso is not None:
            db.delete(curso)
            db.commit()

Nesse código nós temos inicialmente a importação da classe Session do SQLAlchemy que será usada apenas para propósitos de tipagem e logo em seguida temos também a importação da classe Curso que a nossa classe da camada de modelo.

E por fim temos a criação da classe CursoRepository que contém os seguintes métodos estáticos:

  • find_all(db: Session) -> list[Curso]: método responsável por buscar todos os cursos cadastrados;
  • save(db: Session, curso: Curso) -> Curso: método responsável por salvar um curso no banco de dados, esse método foi escrito de forma que ele possa ser utilizado tanto para cadastro de um novo curso, quanto para a edição de um curso já existente;
  • find_by_id(db: Session, id: int) -> Curso: método responsável por buscar um curso no banco de dados com base do id do curso;
  • exists_by_id(db: Session, id: int) -> bool: método responsável por verificar se existe algum curso cadastrado com base no id do curso;
  • delete_by_id(db: Session, id: int) -> None: método responsável por excluir um curso com base no seu id.

Um ponto importante para observarmos é que todos os métodos da camada de repositório recebem como primeiro argumento uma instância de Session que chamamos de db, ou seja, quem chamar os métodos da camada de repositório terá a responsabilidade de prover a sessão do banco de dados.

HTTP - Fundamentos para desenvolvedores
Curso HTTP - Fundamentos para desenvolvedores
Conhecer o curso

Criando a camada de schemas

Agora nós vamos trabalhar em uma camada na qual vamos chamar de schemas, essa camada irá conter classes que irão representar os dados que serão recebidos e retornados no corpo de uma requisição ou resposta HTTP, para criação dessas classes nós vamos utilizar o Pydantic que é uma biblioteca que vem junto com a instalação do FastAPI. O Pydantic tem como objetivo prover uma maneira mais simples e direta para realizar validação de dados através do uso da funcionalidade de Type Hints do Python.

O FastAPI utiliza bastante o Pydantic não só para o processo de validação dos dados, mas também para realizar a tipagem dos dados que são recebidos e retornados nas requisições e respostas HTTP, além disso o FastAPI utiliza essa tipagem na hora de gerar a documentação do projeto.

Só um ponto de observação, essa camada a qual iremos chamar de schemas também recebe o nome de model, porém para não confundirmos os models do SQLAlchemy com os models do Pydantic nós vamos chamar de schemas.

Então vamos começar com a codificação dessa camada, para isso vamos criar um novo arquivo chamado schemas.py e nele iremos colocar o seguinte conteúdo:

from pydantic import BaseModel

class CursoBase(BaseModel):
    titulo: str
    descricao: str
    carga_horaria: int
    qtd_exercicios: int

class CursoRequest(CursoBase):
    ...

class CursoResponse(CursoBase):
    id: int

    class Config:
        orm_mode = True

Nesse código nós inicialmente importamos a classe BaseModel do Pydantic, que é a classe a qual todas as classe de modelo do Pydantic devem herdar, logo em seguida nós criamos três novas classes, a classe CursoBase, a classe CursoRequest e a classe CursoResponse.

A classe CursoBase irá herdar de de BaseModel e nela iremos definir tudo aquilo que é comum para as classes CursoRequest e CursoResponse não evitamos duplicação de código.

Já na classe CursoRequest nós temos tudo aquilo que esperamos receber no corpo das nossas requisições HTTP, como não colocamos nenhum atributo á mais ela só terá aquilo que foi definido na classe CursoBase

E por fim na classe CursoResponse nós temos tudo aquilo que queremos retornar do corpo de nossas respostas HTTP, como definimos apenas o atributo id ela terá os atributos definidos em CursoBase mais o atributo id. Além disso nós também criamos dentro da classe CursoResponse uma classe chamada Config que serve para passarmos configurações adicionais para o nosso modelo do Pydantic e a configuração que fizemos foi colocar como True a opção orm_mode, essa configuração habilita um método estático dentro da classe chamado from_orm que permite a criação de uma instância do modelo do Pydantic a partir de uma classe de modelo da nossa ORM.

Operações de CRUD em uma API com FastAPI e SQLAlchemy

Agora vamos de fato trabalhar no código que vai lidar com as requisições HTTP, incialmente iremos criar um novo arquivo chamado main.py que será o arquivo principal do projeto e onde iremos instanciar a nossa app do FastAPI, nesse arquivo iremos colocar esse conteúdo por enquanto:

from fastapi import FastAPI, Depends, HTTPException, status, Response
from sqlalchemy.orm import Session

from models import Curso
from database import engine, Base, get_db
from repositories import CursoRepository
from schemas import CursoRequest, CursoResponse

Base.metadata.create_all(bind=engine)

app = FastAPI()

Nesse código nós estamos primeiramente importando algumas classes do FastAPI que serão necessárias durante a criação das nossas rotas:

  • FastAPI: classe que instanciamos para criar a app do FastAPI;
  • Depends: classe que é utilizada para realizar o processo de injeção de dependências;
  • HTTPException: classe de exceção que utilizamos para retornar um erro HTTP;
  • status: é um módulo do FastAPI que contém constantes que representam os diferentes status codes do protocolo HTTP;
  • Response: classe do FastAPI que representa uma resposta HTTP.

Além das importações do FastAPI também estamos importando a classe Session do SQLAlchemy que novamente iremos utilizar apenas para tipagem.

Também fazemos as importações da classe Curso da nossa camada de modelo, a classe CursoRepository da nossa camada de repositório, as classes CursoRequest e CursoResponse da nossa camada de schemas, a variável engine, a classe Base e a função get_db da camada de database.

Após realizada todas as importações necessárias temos a instrução Base.metadata.create_all(bind=engine) que vai fazer com que o SQLAlchemy crie o arquivo db.sqlite3 que é o nosso banco de dados SQLite e também crie as tabelas no banco, que no caso é apenas uma, a tabela cursos.

E por fim temos a criação da variável app que é uma instância da classe FastAPI.

APIs Rest - Fundamentos
Curso APIs Rest - Fundamentos
Conhecer o curso

Rota de cadastro de curso

Vamos criar a nossa primeira rota que será a rota responsável por realizar o cadastro de novos cursos em nosso banco de dados, para isso vamos criar uma função que será responsável por tratar a requisição, essa requisição terá que ser feita com o verbo HTTP POST para a rota /api/cursos.

# Código inicial do arquivo omitido para facilitação da leitura

@app.post("/api/cursos", response_model=CursoResponse, status_code=status.HTTP_201_CREATED)
def create(request: CursoRequest, db: Session = Depends(get_db)):
    curso = CursoRepository.save(db, Curso(**request.dict()))
    return CursoResponse.from_orm(curso)

Nesse código temos a função create que será responsável por lidar com as requisições para cadastro de novos usuários, essa função é decorada com o decorator @app.post para informar que é uma rota que lida com requisição do tipo POST. Além disso passamos como primeiro argumento do decorator uma string que informa qual a rota para qual esperamos que seja feita a requisição, depois passamos um parâmetro nomeado chamado response_model que informa qual o tipo de dado estará contido no corpo da resposta, no caso será um dado do tipo CursoResponse e por fim informamos qual o status code da resposta através do parâmetro nomeado status_code.

Já no método create nós definimos um parâmetro chamado request que é do tipo CursoRequest, esse parâmetro recebe os dados que foram passados no corpo da requisição e também temos um segundo parâmetro chamada db do tipo Session que possui como valor padrão a instrução Depends(get_db), nesse momento estamos realizando a injeção de dependências, o FastAPI irá executar a função get_db que irá retornar uma instância da classe LocalSession que é a sessão do banco de dados e irá então passar essa sessão para a nossa função create.

O mais interessante desse processo de injeção de dependências é que ao término do processamento da requisição a sessão será fechada, logo, temos apenas uma sessão do banco de dados por requisição.

Dentro do escopo da função create nós estamos chamando o método estático save da classe CursoRepository e passando para esse método a sessão do banco e os dados do curso a ser criado, logo em seguida nós recebemos os dados curso recém cadastrado e então criamos e retornamos um nova instância de CursoResponse que é criada com o auxílio do método estático from_orm.

Rota de listagem de cursos

Agora vamos implementar a rota que será responsável por listar os cursos cadastrados na aplicação, a requisição a ser feita será para a rota /api/cursos só que agora com o verbo HTTP GET.

# Código inicial do arquivo omitido para facilitação da leitura

@app.get("/api/cursos", response_model=list[CursoResponse])
def find_all(db: Session = Depends(get_db)):
    cursos = CursoRepository.find_all(db)
    return [CursoResponse.from_orm(curso) for curso in cursos]

Agora nós criamos uma função chamada get_all que irá processar as requisições feitas para a rota /api/cursos com o verbo HTTP GET, como o verbo HTTP que iremos trabalhar é o GET então nós utilizamos o decorator @app.get passando qual a rota e qual o tipo que será retornado no corpo da resposta, como iremos retornar uma lista de cursos, então o parâmetro response_model foi definido como list[CursoResponse].

Na função get_all recebemos como parâmetro apenas a sessão do banco de dados via injeção de dependências e então no escopo da função nós chamamos o método estático find_all da classe CursoRepository para que seja feita a busca no banco de dados e logo em seguida criamos e retornamos uma lista de CursoResponse utilizando o recurso de list comprehension do Python.

Veja que nesse caso não utilizamos o parâmetro status_code no decorator @app.get isso por que queremos retornar o status code 200, que significa que a requisição foi processada com sucesso e o status code 200 já é o padrão, então não é necessário informar o status code.

Desenvolvedor Flask Full-Stack
Formação Desenvolvedor Flask Full-Stack
Conhecer a formação

Rota de busca de curso por id

Agora vamos implementar a rota responsável por buscar um curso por id, nessa rota vamos precisar receber o id do curso a ser buscado diretamente pela rota, então a requisição será feita para a rota /api/cursos/{id} onde {id} será substituído pelo id do curso que estamos querendo fazer a busca, ou seja, caso estejamos realizando a busca pelo curso de id 1 a rota será /api/cursos/1 e essa requisição deverá ser realizada com o verbo HTTP GET.

# Código inicial do arquivo omitido para facilitação da leitura

@app.get("/api/cursos/{id}", response_model=CursoResponse)
def find_by_id(id: int, db: Session = Depends(get_db)):
    curso = CursoRepository.find_by_id(db, id)
    if not curso:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Curso não encontrado"
        )
    return CursoResponse.from_orm(curso)

No código acima nós criamos a função find_by_id que irá tratar a requisição, como será uma requisição com verbo GET a função foi decorada com o decorator @app.get onde passamos como primeiro argumento a string informando a rota que é /api/cursos/{id}, onde {id} significa que é uma parte da rota que é variável e definimos o response_model como CursoResponse.

Já a função find_by_id recebe o parâmetro id que será a parte variável da rota, ou seja, o id do curso que estamos buscando e a injeção de dependências da sessão do banco de dados.

No escopo da função nós realizamos a busca no banco utilizando o método estático find_by_id da classe CursoRepository e então verificamos se algum curso foi encontrado, em caso negativo nós lançamos uma exceção do tipo HTTPException, que é uma exceção do FastAPI para quando queremos informar que alguma coisa deu errado e na exceção informamos qual o status code a ser retornado e uma mensagem de erro, já em caso positivo retornamos uma nova instância de CursoResponse com os dados do curso que foi encontrado.

Rota de exclusão de curso por id

Agora vamos para a nossa rota de exclusão de curso por id, essa rota será bem semelhante com a rota de busca de curso por id, a diferença é que ao invés da requisição utilizar o verbo HTTP GET será utilizado o verbo HTTP DELETE.

@app.delete("/api/cursos/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_by_id(id: int, db: Session = Depends(get_db)):
    if not CursoRepository.exists_by_id(db, id):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Curso não encontrado"
        )
    CursoRepository.delete_by_id(db, id)
    return Response(status_code=status.HTTP_204_NO_CONTENT)

Dessa vez como queremos que a requisição seja realizada com o verbo DELETE nós decoramos a função delete_by_id com o decorator @app.delete e passamos como status code a constante HTTP_204_NO_CONTENT, que irá retornar o status code 204, que quer dizer que a requisição foi processada com sucesso, porém não existe nada a ser retornado para quem realizou a requisição.

Já no escopo da função delete_by_id nós verificamos se existe algum curso cadastrado com o id que foi solicitado e em caso negativo lançamos uma exceção do tipo HTTPException com o status code 404 e em caso positivo como não temos nada para retornar no corpo da resposta utilizamos a classe Response do FastAPI para montarmos a resposta de forma manual e apenas dizemos qual o status code da resposta sem nenhum conteúdo.

Rota de atualização de curso

Agora para finalizarmos as nossas operações de CRUD vamos implementar a última rota da nossa API, rota essa que será responsável por realizar a atualização de um curso no banco de dados. Essa rota será bem semelhante às rotas de busca por id e exclusão por id, a diferença será que vamos utilizar o verbo HTTP PUT.

@app.put("/api/cursos/{id}", response_model=CursoResponse)
def update(id: int, request: CursoRequest, db: Session = Depends(get_db)):
    if not CursoRepository.exists_by_id(db, id):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Curso não encontrado"
        )
    curso = CursoRepository.save(db, Curso(id=id, **request.dict()))
    return CursoResponse.from_orm(curso)

Nossa função update responsável por tratar a requisição será decorada com o decorator @app.put para que assim a requisição seja feita com o verbo HTTP PUT, definimos a rota como /api/cursos/{id} e definimos o response_model como CursoResponse.

Já a função update recebe três parâmetros, sendo eles o id do curso que está sendo atualizado, os dados que estarão contidos no corpo da requisição, dados esses que serão utilizados para atualizar os dados do curso em questão e por fim a injeção de dependências da sessão do banco de dados.

No escopo da função nós primeiramente verificamos se o curso que queremos atualizar existe, em caso negativo lançamos a exceção HTTPException com o status code 404 e a mensagem de erro e em caso positivo nós realizamos a operação de atualização com o método estatico save da classe CursoRepository, lembrando que o método save foi feito de forma que possa ser utilizado tanto para uma operação de criação quanto de atualização e por fim retornamos uma nova instância de CursoResponse com os dados do curso já atualizado.

Desenvolvedor Django Full-Stack
Formação Desenvolvedor Django Full-Stack
Conhecer a formação

Resultado final

Após a criação de todas as rotas nós finalizamos a criação da nossa API com FastAPI e SQLAlchemy, o resultado final do código do arquivo main.py ficará da seguinte maneira:

from fastapi import FastAPI, Depends, HTTPException, status, Response
from sqlalchemy.orm import Session

from models import Curso
from database import engine, Base, get_db
from repositories import CursoRepository
from schemas import CursoRequest, CursoResponse

Base.metadata.create_all(bind=engine)

app = FastAPI()

@app.post("/api/cursos", response_model=CursoResponse, status_code=status.HTTP_201_CREATED)
def create(request: CursoRequest, db: Session = Depends(get_db)):
    curso = CursoRepository.save(db, Curso(**request.dict()))
    return CursoResponse.from_orm(curso)

@app.get("/api/cursos", response_model=list[CursoResponse])
def find_all(db: Session = Depends(get_db)):
    cursos = CursoRepository.find_all(db)
    return [CursoResponse.from_orm(curso) for curso in cursos]

@app.get("/api/cursos/{id}", response_model=CursoResponse)
def find_by_id(id: int, db: Session = Depends(get_db)):
    curso = CursoRepository.find_by_id(db, id)
    if not curso:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Curso não encontrado"
        )
    return CursoResponse.from_orm(curso)

@app.delete("/api/cursos/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_by_id(id: int, db: Session = Depends(get_db)):
    if not CursoRepository.exists_by_id(db, id):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Curso não encontrado"
        )
    CursoRepository.delete_by_id(db, id)
    return Response(status_code=status.HTTP_204_NO_CONTENT)

@app.put("/api/cursos/{id}", response_model=CursoResponse)
def update(id: int, request: CursoRequest, db: Session = Depends(get_db)):
    if not CursoRepository.exists_by_id(db, id):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Curso não encontrado"
        )
    curso = CursoRepository.save(db, Curso(id=id, **request.dict()))
    return CursoResponse.from_orm(curso)

Testando nossa API

Agora que temos todas as rotas que realizam as operações de CRUD em nossa API vamos testar para ver se está tudo funcionando corretamente. Para realizar esses testes precisamos de algum programa que consiga realizar requisições HTTP, eu particularmente gosto de utilizar o Insomnia, porém se você possui preferência por outro programa fique à vontade para utilizá-lo.

Primeiro precisamos executar a nossa aplicação, para isso basta executar o seguinte comando no terminal:

uvicorn main:app --reload

Lembrando que esse comando deve ser executado com o ambiente virtual ativado. Alguns instantes após a execução do comando acima você verá uma mensagem parecida com a seguinte:

INFO:     Application startup complete.

Isso significa dizer que aplicação executou com sucesso e já podemos realizar requisições para a nossa API.

Primeiro vamos realizar um cadastro, para isso vamos enviar uma requisição com o verbo HTTP POST para a url http://localhost:8000/api/cursos e no corpo da requisição iremos colocar os dados do curso a ser cadastrado.

No corpo da requisição irei colocar o seguinte JSON:

{
	"titulo": "Python - Fundamentos",
	"descricao": "Python é uma linguagem de altíssimo nível, orientada a objeto, de tipagem dinâmica, fortemente interpretada e interativa.",
	"carga_horaria": 101,
	"qtd_exercicios": 40
}

E ao testar a requisição no Insomnia teremos o seguinte resultado:

Agora vamos realizar a listagem de todos os cursos cadastrados, para isso basta enviar uma requisição com o verbo HTTP GET para a url http://localhost:8000/api/cursos.

Vamos testar também a rota de busca de curso por id, para isso precisamos enviar uma requisição com o verbo HTTP GET para a url http://localhost:8000/api/cursos/1.

Agora vamos testar a rota de atualização de curso por id, para isso vamos enviar uma requisição com o verbo HTTP PUT para url http://localhost:8000/api/cursos/1 e no corpo da requisição vamos enviar os novos dados a serem atualizados.

No corpo da requisição irei colocar o seguinte JSON:

{
	"titulo": "Python - SQLAlchemy ORM",
	"descricao": "Saia do básico e aprenda como realizar um dos melhores ORM do Python",
	"carga_horaria": 251,
	"qtd_exercicios": 33
}

E ao testar a requisição no Insomnia teremos o seguinte resultado:

E por fim vamos testar a nossa rota de exclusão de curso por id, para isso vamos enviar uma requisição com o verbo HTTP DELETE para a url http://localhost:8000/api/cursos/1.

E com isso finalizamos os nossos testes, a API está funcionando perfeitamente. Recomendo que faça mais alguns testes e tente explorar outras possibilidades, como por exemplo, o que será retornado caso passe um id que não existe nas rotas de exclusão, busca por id e de atualização? E o que será que acontece caso deixe de informar alguns dos dados do corpo da requisição nas rotas de cadastro e de atualização?

Outro ponto importante é que o FastAPI gera a documentação do projeto de forma automatica, então não esqueça de acessar o endereço http://localhost:8000/docs para ver como ficou a documentação gerada pelo FastAPI.

OpenAPI - Documentando APIs
Curso OpenAPI - Documentando APIs
Conhecer o curso

Conclusão

Caso queira ver o código-fonte do projeto desenvolvido nesse artigo ele está disponível nesse repositório do GitHub.

Neste artigo vimos como podemos criar uma API com todas as operações de CRUD utilizando o FastAPI e o SQLALchemy.

Nesse artigo vimos vários recursos do SQLAlchemy, mas isso é apenas o começo, existem muitos outros recursos e funcionalidades disponíveis nesta incrível ORM, caso queira aprender mais sobre SQLALchemy saiba que aqui na TreinaWeb nós temos o curso Python - SQLAlchemy ORM que possui 04h11 de vídeos e um total de 33 exercícios. Conheça também nossos outros cursos de Python.

Veja quais são os tópicos abordados durante o curso de Python - SQLAlchemy ORM:

  • O que é o SQLAlchemy e como o ORM funciona;
  • Como instalar o SQLAlchemy utilizando o PIP em qualquer sistema operacional através do PyCharm;
  • Como utilizar o SQLAlchemy para conexão com bancos de dados MySQL;
  • Como mapear entidades utilizando o modo declarativo;
  • Como mapear entidades utilizando os Schemas;
  • Como adicionar, editar e remover registros do banco de dados;
  • Como listar, filtrar, buscar e ordenar registros do banco de dados;
  • Como implementar os relacionamentos 1/N e N/N com o SQLALchemy;
  • Como utilizar o Eager Loading na listagem de dados com relacionamentos;
  • Como configurar o modo CASCADE para garantir a integridade do banco de dados na exclusão de registros.

Autor(a) do artigo

Cleyson Lima
Cleyson Lima

Professor, programador, fã de One Piece e finge saber cozinhar. Cleyson é graduando em Licenciatura em Informática pelo IFPI - Campus Teresina Zona Sul, nos anos de 2019 e 2020 esteve envolvido em vários projetos coordenados pela secretaria municipal de educação da cidade de Teresina, onde o foco era introduzir alunos da rede pública no mundo da programação e robótica. Hoje é instrutor dos cursos de Spring na TreinaWeb, mas diz que seu coração sempre pertencerá ao Python.

Todos os artigos

Artigos relacionados Ver todos