fastapi/docs/pt/docs/tutorial/extra-models.md

212 lines
7.1 KiB
Markdown

# Modelos Adicionais { #extra-models }
Continuando com o exemplo anterior, será comum ter mais de um modelo relacionado.
Isso é especialmente o caso para modelos de usuários, porque:
* O **modelo de entrada** precisa ser capaz de ter uma senha.
* O **modelo de saída** não deve ter uma senha.
* O **modelo de banco de dados** provavelmente precisaria ter uma senha com hash.
/// danger | Cuidado
Nunca armazene senhas em texto simples dos usuários. Sempre armazene uma "hash segura" que você pode verificar depois.
Se não souber, você aprenderá o que é uma "senha hash" nos [capítulos de segurança](security/simple-oauth2.md#password-hashing).
///
## Múltiplos modelos { #multiple-models }
Aqui está uma ideia geral de como os modelos poderiam parecer com seus campos de senha e os lugares onde são usados:
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
### Sobre `**user_in.model_dump()` { #about-user-in-model-dump }
#### O `.model_dump()` do Pydantic { #pydantics-model-dump }
`user_in` é um modelo Pydantic da classe `UserIn`.
Os modelos Pydantic possuem um método `.model_dump()` que retorna um `dict` com os dados do modelo.
Então, se criarmos um objeto Pydantic `user_in` como:
```Python
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
```
e depois chamarmos:
```Python
user_dict = user_in.model_dump()
```
agora temos um `dict` com os dados na variável `user_dict` (é um `dict` em vez de um objeto de modelo Pydantic).
E se chamarmos:
```Python
print(user_dict)
```
teríamos um `dict` Python com:
```Python
{
'username': 'john',
'password': 'secret',
'email': 'john.doe@example.com',
'full_name': None,
}
```
#### Desembrulhando um `dict` { #unpacking-a-dict }
Se tomarmos um `dict` como `user_dict` e passarmos para uma função (ou classe) com `**user_dict`, o Python irá "desembrulhá-lo". Ele passará as chaves e valores do `user_dict` diretamente como argumentos chave-valor.
Então, continuando com o `user_dict` acima, escrevendo:
```Python
UserInDB(**user_dict)
```
Resultaria em algo equivalente a:
```Python
UserInDB(
username="john",
password="secret",
email="john.doe@example.com",
full_name=None,
)
```
Ou mais exatamente, usando `user_dict` diretamente, com qualquer conteúdo que ele possa ter no futuro:
```Python
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
)
```
#### Um modelo Pydantic a partir do conteúdo de outro { #a-pydantic-model-from-the-contents-of-another }
Como no exemplo acima, obtivemos o `user_dict` a partir do `user_in.model_dump()`, este código:
```Python
user_dict = user_in.model_dump()
UserInDB(**user_dict)
```
seria equivalente a:
```Python
UserInDB(**user_in.model_dump())
```
...porque `user_in.model_dump()` é um `dict`, e depois fazemos o Python "desembrulhá-lo" passando-o para `UserInDB` precedido por `**`.
Então, obtemos um modelo Pydantic a partir dos dados em outro modelo Pydantic.
#### Desembrulhando um `dict` e palavras-chave extras { #unpacking-a-dict-and-extra-keywords }
E, então, adicionando o argumento de palavra-chave extra `hashed_password=hashed_password`, como em:
```Python
UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
```
...acaba sendo como:
```Python
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
hashed_password = hashed_password,
)
```
/// warning | Atenção
As funções adicionais de suporte `fake_password_hasher` e `fake_save_user` servem apenas para demonstrar um fluxo possível dos dados, mas é claro que elas não fornecem segurança real.
///
## Reduzir duplicação { #reduce-duplication }
Reduzir a duplicação de código é uma das ideias principais no **FastAPI**.
A duplicação de código aumenta as chances de bugs, problemas de segurança, problemas de desincronização de código (quando você atualiza em um lugar, mas não em outros), etc.
E esses modelos estão compartilhando muitos dos dados e duplicando nomes e tipos de atributos.
Nós poderíamos fazer melhor.
Podemos declarar um modelo `UserBase` que serve como base para nossos outros modelos. E então podemos fazer subclasses desse modelo que herdam seus atributos (declarações de tipo, validação, etc.).
Toda conversão de dados, validação, documentação, etc. ainda funcionará normalmente.
Dessa forma, podemos declarar apenas as diferenças entre os modelos (com `password` em texto claro, com `hashed_password` e sem senha):
{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}
## `Union` ou `anyOf` { #union-or-anyof }
Você pode declarar uma resposta como o `Union` de dois ou mais tipos, o que significa que a resposta seria qualquer um deles.
Isso será definido no OpenAPI com `anyOf`.
Para fazer isso, use a anotação de tipo padrão do Python [`typing.Union`](https://docs.python.org/3/library/typing.html#typing.Union):
/// note | Nota
Ao definir um [`Union`](https://docs.pydantic.dev/latest/concepts/types/#unions), inclua o tipo mais específico primeiro, seguido pelo tipo menos específico. No exemplo abaixo, o tipo mais específico `PlaneItem` vem antes de `CarItem` em `Union[PlaneItem, CarItem]`.
///
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
### `Union` no Python 3.10 { #union-in-python-3-10 }
Neste exemplo, passamos `Union[PlaneItem, CarItem]` como o valor do argumento `response_model`.
Dado que estamos passando-o como um **valor para um argumento** em vez de colocá-lo em uma **anotação de tipo**, precisamos usar `Union` mesmo no Python 3.10.
Se estivesse em uma anotação de tipo, poderíamos ter usado a barra vertical, como:
```Python
some_variable: PlaneItem | CarItem
```
Mas se colocarmos isso na atribuição `response_model=PlaneItem | CarItem`, teríamos um erro, pois o Python tentaria executar uma **operação inválida** entre `PlaneItem` e `CarItem` em vez de interpretar isso como uma anotação de tipo.
## Lista de modelos { #list-of-models }
Da mesma forma, você pode declarar respostas de listas de objetos.
Para isso, use o padrão Python `list`:
{* ../../docs_src/extra_models/tutorial004_py310.py hl[18] *}
## Resposta com `dict` arbitrário { #response-with-arbitrary-dict }
Você também pode declarar uma resposta usando um simples `dict` arbitrário, declarando apenas o tipo das chaves e valores, sem usar um modelo Pydantic.
Isso é útil se você não souber os nomes de campo / atributo válidos (que seriam necessários para um modelo Pydantic) antecipadamente.
Neste caso, você pode usar `dict`:
{* ../../docs_src/extra_models/tutorial005_py310.py hl[6] *}
## Recapitulação { #recap }
Use vários modelos Pydantic e herde livremente para cada caso.
Não é necessário ter um único modelo de dados por entidade se essa entidade precisar ter diferentes "estados". No caso da "entidade" de usuário com um estado que inclui `password`, `password_hash` e sem senha.