committed by
GitHub
42 changed files with 806 additions and 1441 deletions
@ -0,0 +1,33 @@ |
|||||
|
# 응답 - 상태 코드 변경 |
||||
|
|
||||
|
기본 [응답 상태 코드 설정](../tutorial/response-status-code.md){.internal-link target=_blank}이 가능하다는 걸 이미 알고 계실 겁니다. |
||||
|
|
||||
|
하지만 경우에 따라 기본 설정과 다른 상태 코드를 반환해야 할 때가 있습니다. |
||||
|
|
||||
|
## 사용 예 |
||||
|
|
||||
|
예를 들어 기본적으로 HTTP 상태 코드 "OK" `200`을 반환하고 싶다고 가정해 봅시다. |
||||
|
|
||||
|
하지만 데이터가 존재하지 않으면 이를 새로 생성하고, HTTP 상태 코드 "CREATED" `201`을 반환하고자 할 때가 있을 수 있습니다. |
||||
|
|
||||
|
이때도 여전히 `response_model`을 사용하여 반환하는 데이터를 필터링하고 변환하고 싶을 수 있습니다. |
||||
|
|
||||
|
이런 경우에는 `Response` 파라미터를 사용할 수 있습니다. |
||||
|
|
||||
|
## `Response` 파라미터 사용하기 |
||||
|
|
||||
|
*경로 작동 함수*에 `Response` 타입의 파라미터를 선언할 수 있습니다. (쿠키와 헤더에 대해 선언하는 것과 유사하게) |
||||
|
|
||||
|
그리고 이 *임시* 응답 객체에서 `status_code`를 설정할 수 있습니다. |
||||
|
|
||||
|
```Python hl_lines="1 9 12" |
||||
|
{!../../docs_src/response_change_status_code/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
그리고 평소처럼 원하는 객체(`dict`, 데이터베이스 모델 등)를 반환할 수 있습니다. |
||||
|
|
||||
|
`response_model`을 선언했다면 반환된 객체는 여전히 필터링되고 변환됩니다. |
||||
|
|
||||
|
**FastAPI**는 이 *임시* 응답 객체에서 상태 코드(쿠키와 헤더 포함)를 추출하여, `response_model`로 필터링된 반환 값을 최종 응답에 넣습니다. |
||||
|
|
||||
|
또한, 의존성에서도 `Response` 파라미터를 선언하고 그 안에서 상태 코드를 설정할 수 있습니다. 단, 마지막으로 설정된 상태 코드가 우선 적용된다는 점을 유의하세요. |
@ -0,0 +1,34 @@ |
|||||
|
# 벤치마크 |
||||
|
|
||||
|
독립적인 TechEmpower 벤치마크에 따르면 **FastAPI** 애플리케이션이 Uvicorn을 사용하여 <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">가장 빠른 Python 프레임워크 중 하나</a>로 실행되며, Starlette와 Uvicorn 자체(내부적으로 FastAPI가 사용하는 도구)보다 조금 아래에 위치합니다. |
||||
|
|
||||
|
그러나 벤치마크와 비교를 확인할 때 다음 사항을 염두에 두어야 합니다. |
||||
|
|
||||
|
## 벤치마크와 속도 |
||||
|
|
||||
|
벤치마크를 확인할 때, 일반적으로 여러 가지 유형의 도구가 동등한 것으로 비교되는 것을 볼 수 있습니다. |
||||
|
|
||||
|
특히, Uvicorn, Starlette, FastAPI가 함께 비교되는 경우가 많습니다(다른 여러 도구와 함께). |
||||
|
|
||||
|
도구가 해결하는 문제가 단순할수록 성능이 더 좋아집니다. 그리고 대부분의 벤치마크는 도구가 제공하는 추가 기능을 테스트하지 않습니다. |
||||
|
|
||||
|
계층 구조는 다음과 같습니다: |
||||
|
|
||||
|
* **Uvicorn**: ASGI 서버 |
||||
|
* **Starlette**: (Uvicorn 사용) 웹 마이크로 프레임워크 |
||||
|
* **FastAPI**: (Starlette 사용) API 구축을 위한 데이터 검증 등 여러 추가 기능이 포함된 API 마이크로 프레임워크 |
||||
|
|
||||
|
* **Uvicorn**: |
||||
|
* 서버 자체 외에는 많은 추가 코드가 없기 때문에 최고의 성능을 발휘합니다. |
||||
|
* 직접 Uvicorn으로 응용 프로그램을 작성하지는 않을 것입니다. 즉, 사용자의 코드에는 적어도 Starlette(또는 **FastAPI**)에서 제공하는 모든 코드가 포함되어야 합니다. 그렇게 하면 최종 응용 프로그램은 프레임워크를 사용하고 앱 코드와 버그를 최소화하는 것과 동일한 오버헤드를 갖게 됩니다. |
||||
|
* Uvicorn을 비교할 때는 Daphne, Hypercorn, uWSGI 등의 응용 프로그램 서버와 비교하세요. |
||||
|
* **Starlette**: |
||||
|
* Uvicorn 다음으로 좋은 성능을 발휘합니다. 사실 Starlette는 Uvicorn을 사용하여 실행됩니다. 따라서 더 많은 코드를 실행해야 하기 때문에 Uvicorn보다 "느려질" 수밖에 없습니다. |
||||
|
* 하지만 경로 기반 라우팅 등 간단한 웹 응용 프로그램을 구축할 수 있는 도구를 제공합니다. |
||||
|
* Starlette를 비교할 때는 Sanic, Flask, Django 등의 웹 프레임워크(또는 마이크로 프레임워크)와 비교하세요. |
||||
|
* **FastAPI**: |
||||
|
* Starlette가 Uvicorn을 사용하므로 Uvicorn보다 빨라질 수 없는 것과 마찬가지로, **FastAPI**는 Starlette를 사용하므로 더 빠를 수 없습니다. |
||||
|
* FastAPI는 Starlette에 추가적으로 더 많은 기능을 제공합니다. API를 구축할 때 거의 항상 필요한 데이터 검증 및 직렬화와 같은 기능들이 포함되어 있습니다. 그리고 이를 사용하면 문서 자동화 기능도 제공됩니다(문서 자동화는 응용 프로그램 실행 시 오버헤드를 추가하지 않고 시작 시 생성됩니다). |
||||
|
* FastAPI를 사용하지 않고 직접 Starlette(또는 Sanic, Flask, Responder 등)를 사용했다면 데이터 검증 및 직렬화를 직접 구현해야 합니다. 따라서 최종 응용 프로그램은 FastAPI를 사용한 것과 동일한 오버헤드를 가지게 될 것입니다. 많은 경우 데이터 검증 및 직렬화가 응용 프로그램에서 작성된 코드 중 가장 많은 부분을 차지합니다. |
||||
|
* 따라서 FastAPI를 사용함으로써 개발 시간, 버그, 코드 라인을 줄일 수 있으며, FastAPI를 사용하지 않았을 때와 동일하거나 더 나은 성능을 얻을 수 있습니다(코드에서 모두 구현해야 하기 때문에). |
||||
|
* FastAPI를 비교할 때는 Flask-apispec, NestJS, Molten 등 데이터 검증, 직렬화 및 문서화가 통합된 자동 데이터 검증, 직렬화 및 문서화를 제공하는 웹 응용 프로그램 프레임워크(또는 도구 집합)와 비교하세요. |
@ -0,0 +1,83 @@ |
|||||
|
# FastAPI CLI |
||||
|
|
||||
|
**FastAPI CLI**는 FastAPI 애플리케이션을 실행하고, 프로젝트를 관리하는 등 다양한 작업을 수행할 수 있는 커맨드 라인 프로그램입니다. |
||||
|
|
||||
|
FastAPI를 설치할 때 (예: `pip install "fastapi[standard]"` 명령어를 사용할 경우), `fastapi-cli`라는 패키지가 포함됩니다. 이 패키지는 터미널에서 사용할 수 있는 `fastapi` 명령어를 제공합니다. |
||||
|
|
||||
|
개발용으로 FastAPI 애플리케이션을 실행하려면 다음과 같이 `fastapi dev` 명령어를 사용할 수 있습니다: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u> |
||||
|
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font> |
||||
|
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font> |
||||
|
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files |
||||
|
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font> |
||||
|
|
||||
|
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮ |
||||
|
│ │ |
||||
|
│ 🐍 main.py │ |
||||
|
│ │ |
||||
|
╰──────────────────────╯ |
||||
|
|
||||
|
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font> |
||||
|
<font color="#3465A4">INFO </font> Found importable FastAPI app |
||||
|
|
||||
|
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮ |
||||
|
│ │ |
||||
|
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │ |
||||
|
│ │ |
||||
|
╰──────────────────────────╯ |
||||
|
|
||||
|
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font> |
||||
|
|
||||
|
<span style="background-color:#C4A000"><font color="#2E3436">╭────────── FastAPI CLI - Development mode ───────────╮</font></span> |
||||
|
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
||||
|
<span style="background-color:#C4A000"><font color="#2E3436">│ Serving at: http://127.0.0.1:8000 │</font></span> |
||||
|
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
||||
|
<span style="background-color:#C4A000"><font color="#2E3436">│ API docs: http://127.0.0.1:8000/docs │</font></span> |
||||
|
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
||||
|
<span style="background-color:#C4A000"><font color="#2E3436">│ Running in development mode, for production use: │</font></span> |
||||
|
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
||||
|
<span style="background-color:#C4A000"><font color="#2E3436">│ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> │</font></span> |
||||
|
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span> |
||||
|
<span style="background-color:#C4A000"><font color="#2E3436">╰─────────────────────────────────────────────────────╯</font></span> |
||||
|
|
||||
|
<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] |
||||
|
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit) |
||||
|
<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font> |
||||
|
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>] |
||||
|
<font color="#4E9A06">INFO</font>: Waiting for application startup. |
||||
|
<font color="#4E9A06">INFO</font>: Application startup complete. |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
`fastapi`라고 불리는 명령어 프로그램은 **FastAPI CLI**입니다. |
||||
|
|
||||
|
FastAPI CLI는 Python 프로그램의 경로(예: `main.py`)를 인수로 받아, `FastAPI` 인스턴스(일반적으로 `app`으로 명명)를 자동으로 감지하고 올바른 임포트 과정을 결정한 후 이를 실행합니다. |
||||
|
|
||||
|
프로덕션 환경에서는 `fastapi run` 명령어를 사용합니다. 🚀 |
||||
|
|
||||
|
내부적으로, **FastAPI CLI**는 고성능의, 프로덕션에 적합한, ASGI 서버인 <a href="https://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a>을 사용합니다. 😎 |
||||
|
|
||||
|
## `fastapi dev` |
||||
|
|
||||
|
`fastapi dev` 명령을 실행하면 개발 모드가 시작됩니다. |
||||
|
|
||||
|
기본적으로 **자동 재시작(auto-reload)** 기능이 활성화되어, 코드에 변경이 생기면 서버를 자동으로 다시 시작합니다. 하지만 이 기능은 리소스를 많이 사용하며, 비활성화했을 때보다 안정성이 떨어질 수 있습니다. 따라서 개발 환경에서만 사용하는 것이 좋습니다. 또한, 서버는 컴퓨터가 자체적으로 통신할 수 있는 IP 주소(`localhost`)인 `127.0.0.1`에서 연결을 대기합니다. |
||||
|
|
||||
|
## `fastapi run` |
||||
|
|
||||
|
`fastapi run` 명령을 실행하면 기본적으로 프로덕션 모드로 FastAPI가 시작됩니다. |
||||
|
|
||||
|
기본적으로 **자동 재시작(auto-reload)** 기능이 비활성화되어 있습니다. 또한, 사용 가능한 모든 IP 주소인 `0.0.0.0`에서 연결을 대기하므로 해당 컴퓨터와 통신할 수 있는 모든 사람이 공개적으로 액세스할 수 있습니다. 이는 일반적으로 컨테이너와 같은 프로덕션 환경에서 실행하는 방법입니다. |
||||
|
|
||||
|
애플리케이션을 배포하는 방식에 따라 다르지만, 대부분 "종료 프록시(termination proxy)"를 활용해 HTTPS를 처리하는 것이 좋습니다. 배포 서비스 제공자가 이 작업을 대신 처리해줄 수도 있고, 직접 설정해야 할 수도 있습니다. |
||||
|
|
||||
|
/// tip |
||||
|
|
||||
|
자세한 내용은 [deployment documentation](deployment/index.md){.internal-link target=\_blank}에서 확인할 수 있습니다. |
||||
|
|
||||
|
/// |
@ -0,0 +1,359 @@ |
|||||
|
# 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 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a>. |
||||
|
|
||||
|
**SQLModel** é construído sobre <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> 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 <abbr title="Object Relational Mapper, um termo sofisticado para uma biblioteca onde algumas classes representam tabelas SQL e instâncias representam linhas nessas tabelas">"ORMs"</abbr>), 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: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a> |
||||
|
|
||||
|
/// |
||||
|
|
||||
|
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 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">documentação do SQLModel</a>. |
||||
|
|
||||
|
## Instalar o `SQLModel` |
||||
|
|
||||
|
Primeiro, certifique-se de criar seu [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e, em seguida, instalar o `sqlmodel`: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pip install sqlmodel |
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
## 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:11] hl[7:11] *} |
||||
|
|
||||
|
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 o `id` é 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 um `INTEGER` no banco de dados SQL e que ela deve ser `NULLABLE`. |
||||
|
|
||||
|
* `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 tipo `TEXT` (ou `VARCHAR`, 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[14:18] hl[14:15,17:18] *} |
||||
|
|
||||
|
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:22] hl[21:22] *} |
||||
|
|
||||
|
### 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[32:37] hl[35:37] *} |
||||
|
|
||||
|
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 <a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a> 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[40:45] hl[40:45] *} |
||||
|
|
||||
|
</details> |
||||
|
|
||||
|
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[48:55] hl[51:52,54] *} |
||||
|
|
||||
|
### Ler um Único Hero |
||||
|
|
||||
|
Podemos **ler** um único `Hero`. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} |
||||
|
|
||||
|
### Deletar um Hero |
||||
|
|
||||
|
Também podemos **deletar** um `Hero`. |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} |
||||
|
|
||||
|
### Executar o App |
||||
|
|
||||
|
Você pode executar o app: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ fastapi dev main.py |
||||
|
|
||||
|
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/sql-databases/image01.png"> |
||||
|
</div> |
||||
|
|
||||
|
## 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[7:9] hl[7:9] *} |
||||
|
|
||||
|
#### `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[7:14] hl[12:14] *} |
||||
|
|
||||
|
#### `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[7:18] hl[17:18] *} |
||||
|
|
||||
|
#### `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[7:22] hl[21:22] *} |
||||
|
|
||||
|
#### `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[7:28] hl[25:28] *} |
||||
|
|
||||
|
### 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[56:62] hl[56:58] *} |
||||
|
|
||||
|
/// 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[65:72] hl[65] *} |
||||
|
|
||||
|
### Ler Um Hero com `HeroPublic` |
||||
|
|
||||
|
Podemos **ler** um único herói: |
||||
|
|
||||
|
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} |
||||
|
|
||||
|
### 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[83:93] hl[83:84,88:89] *} |
||||
|
|
||||
|
### 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[96:103] hl[101] *} |
||||
|
|
||||
|
### Executar o App Novamente |
||||
|
|
||||
|
Você pode executar o app novamente: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ fastapi dev main.py |
||||
|
|
||||
|
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
<div class="screenshot"> |
||||
|
<img src="/img/tutorial/sql-databases/image02.png"> |
||||
|
</div> |
||||
|
|
||||
|
## Recapitulando |
||||
|
|
||||
|
Você pode usar <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a> 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 <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">tutorial sobre como usar SQLModel com **FastAPI**</a> mais longo. 🚀 |
Loading…
Reference in new issue