16 KiB
Bancos de Dados SQL (Relacionais)
FastAPI não exige que você use um banco de dados SQL (relacional). Mas você pode usar qualquer banco de dados que quiser.
Aqui veremos um exemplo usando SQLModel.
SQLModel é construído sobre SQLAlchemy e Pydantic. Ele foi criado pelo mesmo autor do FastAPI para ser o par perfeito para aplicações FastAPI que precisam usar bancos de dados SQL.
/// tip | Dica
Você pode usar qualquer outra biblioteca de banco de dados SQL ou NoSQL que quiser (em alguns casos chamadas de "ORMs"), o FastAPI não obriga você a usar nada. 😎
///
Como o SQLModel é baseado no SQLAlchemy, você pode facilmente usar qualquer banco de dados suportado pelo SQLAlchemy (o que também os torna suportados pelo SQLModel), como:
- PostgreSQL
- MySQL
- SQLite
- Oracle
- Microsoft SQL Server, etc.
Neste exemplo, usaremos SQLite, porque ele usa um único arquivo e o Python tem suporte integrado. Assim, você pode copiar este exemplo e executá-lo como está.
Mais tarde, para sua aplicação em produção, você pode querer usar um servidor de banco de dados como o PostgreSQL.
/// tip | Dica
Existe um gerador de projetos oficial com FastAPI e PostgreSQL incluindo um frontend e mais ferramentas: https://github.com/fastapi/full-stack-fastapi-template
///
Este é um tutorial muito simples e curto, se você quiser aprender sobre bancos de dados em geral, sobre SQL ou recursos mais avançados, acesse a documentação do SQLModel.
Instalar o SQLModel
Primeiro, certifique-se de criar seu ambiente virtual{.internal-link target=_blank}, ativá-lo e, em seguida, instalar o sqlmodel
:
$ pip install sqlmodel
---> 100%
Criar o App com um Único Modelo
Vamos criar a primeira versão mais simples do app com um único modelo SQLModel.
Depois, vamos melhorá-lo aumentando a segurança e versatilidade com múltiplos modelos abaixo. 🤓
Criar Modelos
Importe o SQLModel
e crie um modelo de banco de dados:
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:12] hl[8:12] *}
A classe Hero
é muito semelhante a um modelo Pydantic (na verdade, por baixo dos panos, ela é um modelo Pydantic).
Existem algumas diferenças:
-
table=True
informa ao SQLModel que este é um modelo de tabela, ele deve representar uma tabela no banco de dados SQL, não é apenas um modelo de dados (como seria qualquer outra classe Pydantic comum). -
Field(primary_key=True)
informa ao SQLModel que oid
é a chave primária no banco de dados SQL (você pode aprender mais sobre chaves primárias SQL na documentação do SQLModel).Ao ter o tipo como
int | None
, o SQLModel saberá que essa coluna deve ser umINTEGER
no banco de dados SQL e que ela deve serNULLABLE
. -
Field(index=True)
informa ao SQLModel que ele deve criar um índice SQL para essa coluna, o que permitirá buscas mais rápidas no banco de dados ao ler dados filtrados por essa coluna.O SQLModel saberá que algo declarado como
str
será uma coluna SQL do tipoTEXT
(ouVARCHAR
, dependendo do banco de dados).
Criar um Engine
Um engine
SQLModel (por baixo dos panos, ele é na verdade um engine
do SQLAlchemy) é o que mantém as conexões com o banco de dados.
Você teria um único objeto engine
para todo o seu código se conectar ao mesmo banco de dados.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[15:19] hl[15:16,18:19] *}
Usar check_same_thread=False
permite que o FastAPI use o mesmo banco de dados SQLite em diferentes threads. Isso é necessário, pois **uma única requisição** pode usar **mais de uma thread** (por exemplo, em dependências).
Não se preocupe, com a forma como o código está estruturado, garantiremos que usamos uma única sessão SQLModel por requisição mais tarde, isso é realmente o que o check_same_thread
está tentando conseguir.
Criar as Tabelas
Em seguida, adicionamos uma função que usa SQLModel.metadata.create_all(engine)
para criar as tabelas para todos os modelos de tabela.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:23] hl[22:23] *}
Criar uma Dependência de Sessão
Uma Session
é o que armazena os objetos na memória e acompanha as alterações necessárias nos dados, para então usar o engine
para se comunicar com o banco de dados.
Vamos criar uma dependência do FastAPI com yield
que fornecerá uma nova Session
para cada requisição. Isso é o que garante que usamos uma única sessão por requisição. 🤓
Então, criamos uma dependência Annotated
chamada SessionDep
para simplificar o restante do código que usará essa dependência.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *}
Criar Tabelas de Banco de Dados na Inicialização
Vamos criar as tabelas do banco de dados quando o aplicativo for iniciado.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[34:42] hl[34:37, 42] *}
Aqui, criamos as tabelas em um evento de inicialização do aplicativo.
Para produção, você provavelmente usaria um script de migração que é executado antes de iniciar seu app. 🤓
/// tip | Dica
O SQLModel terá utilitários de migração envolvendo o Alembic, mas por enquanto, você pode usar o Alembic diretamente.
///
Criar um Hero
Como cada modelo SQLModel também é um modelo Pydantic, você pode usá-lo nas mesmas anotações de tipo que usaria para modelos Pydantic.
Por exemplo, se você declarar um parâmetro do tipo Hero
, ele será lido do corpo JSON.
Da mesma forma, você pode declará-lo como o tipo de retorno da função, e então o formato dos dados aparecerá na interface de documentação automática da API.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[45:50] hl[45:50] *}
Aqui, usamos a dependência SessionDep
(uma Session
) para adicionar o novo Hero
à instância Session
, fazer commit das alterações no banco de dados, atualizar os dados no hero
e então retorná-lo.
Ler Heroes
Podemos ler Hero
s do banco de dados usando um select()
. Podemos incluir um limit
e offset
para paginar os resultados.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[53:60] hl[56:57,59] *}
Ler um Único Hero
Podemos ler um único Hero
.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[63:68] hl[65] *}
Deletar um Hero
Também podemos deletar um Hero
.
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[71:78] hl[76] *}
Executar o App
Você pode executar o app:
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Então, vá para a interface /docs
, você verá que o FastAPI está usando esses modelos para documentar a API, e ele também os usará para serializar e validar os dados.

Atualizar o App com Múltiplos Modelos
Agora vamos refatorar este app um pouco para aumentar a segurança e versatilidade.
Se você verificar o app anterior, na interface você pode ver que, até agora, ele permite que o cliente decida o id
do Hero
a ser criado. 😱
Não deveríamos deixar isso acontecer, eles poderiam sobrescrever um id
que já atribuimos na base de dados. Decidir o id
deve ser feito pelo backend ou pelo banco de dados, não pelo cliente.
Além disso, criamos um secret_name
para o hero, mas até agora estamos retornando ele em todos os lugares, isso não é muito secreto... 😅
Vamos corrigir essas coisas adicionando alguns modelos extras. Aqui é onde o SQLModel vai brilhar. ✨
Criar Múltiplos Modelos
No SQLModel, qualquer classe de modelo que tenha table=True
é um modelo de tabela.
E qualquer classe de modelo que não tenha table=True
é um modelo de dados, esses são na verdade apenas modelos Pydantic (com alguns recursos extras pequenos). 🤓
Com o SQLModel, podemos usar a herança para evitar duplicação de todos os campos em todos os casos.
HeroBase
- a classe base
Vamos começar com um modelo HeroBase
que tem todos os campos compartilhados por todos os modelos:
name
age
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[8:10] hl[8:10] *}
Hero
- o modelo de tabela
Em seguida, vamos criar Hero
, o verdadeiro modelo de tabela, com os campos extras que nem sempre estão nos outros modelos:
id
secret_name
Como Hero
herda de HeroBase
, ele também tem os campos declarados em HeroBase
, então todos os campos para Hero
são:
id
name
age
secret_name
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[8:15] hl[13:15] *}
HeroPublic
- o modelo de dados público
Em seguida, criamos um modelo HeroPublic
, que será retornado para os clientes da API.
Ele tem os mesmos campos que HeroBase
, então não incluirá secret_name
.
Finalmente, a identidade dos nossos heróis está protegida! 🥷
Ele também declara novamente id: int
. Ao fazer isso, estamos fazendo um contrato com os clientes da API, para que eles possam sempre esperar que o id
estará lá e será um int
(nunca será None
).
/// tip | Dica
Fazer com que o modelo de retorno garanta que um valor esteja sempre disponível e sempre seja um int
(não None
) é muito útil para os clientes da API, eles podem escrever código muito mais simples com essa certeza.
Além disso, clientes gerados automaticamente terão interfaces mais simples, para que os desenvolvedores que se comunicam com sua API possam ter uma experiência muito melhor trabalhando com sua API. 😎
///
Todos os campos em HeroPublic
são os mesmos que em HeroBase
, com id
declarado como int
(não None
):
id
name
age
secret_name
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[8:19] hl[18:19] *}
HeroCreate
- o modelo de dados para criar um hero
Agora criamos um modelo HeroCreate
, este é o que validará os dados dos clientes.
Ele tem os mesmos campos que HeroBase
, e também tem secret_name
.
Agora, quando os clientes criarem um novo hero, eles enviarão o secret_name
, ele será armazenado no banco de dados, mas esses nomes secretos não serão retornados na API para os clientes.
/// tip | Dica
É assim que você trataria senhas. Receba-as, mas não as retorne na API.
Você também faria um hash com os valores das senhas antes de armazená-los, nunca os armazene em texto simples.
///
Os campos de HeroCreate
são:
name
age
secret_name
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[8:23] hl[22:23] *}
HeroUpdate
- o modelo de dados para atualizar um hero
Não tínhamos uma maneira de atualizar um hero na versão anterior do app, mas agora com múltiplos modelos, podemos fazer isso. 🎉
O modelo de dados HeroUpdate
é um pouco especial, ele tem todos os mesmos campos que seriam necessários para criar um novo hero, mas todos os campos são opcionais (todos têm um valor padrão). Dessa forma, quando você atualizar um hero, poderá enviar apenas os campos que deseja atualizar.
Como todos os campos realmente mudam (o tipo agora inclui None
e eles agora têm um valor padrão de None
), precisamos declarar novamente todos eles.
Não precisamos herdar de HeroBase
, pois estamos redeclarando todos os campos. Vou deixá-lo herdando apenas por consistência, mas isso não é necessário. É mais uma questão de gosto pessoal. 🤷
Os campos de HeroUpdate
são:
name
age
secret_name
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[8:29] hl[26:29] *}
Criar com HeroCreate
e retornar um HeroPublic
Agora que temos múltiplos modelos, podemos atualizar as partes do app que os utilizam.
Recebemos na requisição um modelo de dados HeroCreate
, e a partir dele, criamos um modelo de tabela Hero
.
Esse novo modelo de tabela Hero
terá os campos enviados pelo cliente, e também terá um id
gerado pelo banco de dados.
Em seguida, retornamos o mesmo modelo de tabela Hero
como está na função. Mas como declaramos o response_model
com o modelo de dados HeroPublic
, o FastAPI usará HeroPublic
para validar e serializar os dados.
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[61:67] hl[61:63] *}
/// tip | Dica
Agora usamos response_model=HeroPublic
em vez da anotação de tipo de retorno -> HeroPublic
porque o valor que estamos retornando na verdade não é um HeroPublic
.
Se tivéssemos declarado -> HeroPublic
, seu editor e o linter reclamariam (com razão) que você está retornando um Hero
em vez de um HeroPublic
.
Ao declará-lo no response_model
, estamos dizendo ao FastAPI para fazer o seu trabalho, sem interferir nas anotações de tipo e na ajuda do seu editor e de outras ferramentas.
///
Ler Heroes com HeroPublic
Podemos fazer o mesmo que antes para ler Hero
s, novamente, usamos response_model=list[HeroPublic]
para garantir que os dados sejam validados e serializados corretamente.
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[70:77] hl[70] *}
Ler Um Hero com HeroPublic
Podemos ler um único herói:
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[80:85] hl[82] *}
Atualizar um Hero com HeroUpdate
Podemos atualizar um hero. Para isso, usamos uma operação HTTP PATCH
.
E no código, obtemos um dict
com todos os dados enviados pelo cliente, apenas os dados enviados pelo cliente, excluindo quaisquer valores que estariam lá apenas por serem os valores padrão. Para fazer isso, usamos exclude_unset=True
. Este é o truque principal. 🪄
Em seguida, usamos hero_db.sqlmodel_update(hero_data)
para atualizar o hero_db
com os dados de hero_data
.
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[88:98] hl[88:89,93:94] *}
Deletar um Hero Novamente
Deletar um hero permanece praticamente o mesmo.
Não vamos satisfazer o desejo de refatorar tudo neste aqui. 😅
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[101:108] hl[106] *}
Executar o App Novamente
Você pode executar o app novamente:
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
If you go to the /docs
API UI, you will see that it is now updated, and it won't expect to receive the id
from the client when creating a hero, etc.

Recapitulando
Você pode usar SQLModel para interagir com um banco de dados SQL e simplificar o código com modelos de dados e modelos de tabela.
Você pode aprender muito mais na documentação do SQLModel, há um mini tutorial sobre como usar SQLModel com FastAPI mais longo. 🚀