committed by
GitHub
1 changed files with 349 additions and 0 deletions
@ -0,0 +1,349 @@ |
|||
# Dependências com yield |
|||
|
|||
O FastAPI possui suporte para dependências que realizam <abbr title='também chamados de "código de saída", "código de cleanup", "código de teardown", "código de finalização", "código de saída para gerenciador de contextos", etc.'>alguns passos extras ao finalizar</abbr>. |
|||
|
|||
Para fazer isso, utilize `yield` em vez de `return`, e escreva os passos extras (código) depois. |
|||
|
|||
!!! tip "Dica" |
|||
Garanta que `yield` é utilizado apenas uma vez. |
|||
|
|||
!!! note "Detalhes Técnicos" |
|||
Qualquer função que possa ser utilizada com: |
|||
|
|||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> ou |
|||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a> |
|||
|
|||
pode ser utilizada como uma dependência do **FastAPI**. |
|||
|
|||
Na realidade, o FastAPI utiliza esses dois decoradores internamente. |
|||
|
|||
## Uma dependência de banco de dados com `yield` |
|||
|
|||
Por exemplo, você poderia utilizar isso para criar uma sessão do banco de dados, e fechá-la após terminar sua operação. |
|||
|
|||
Apenas o código anterior a declaração com `yield` e o código contendo essa declaração são executados antes de criar uma resposta. |
|||
|
|||
```Python hl_lines="2-4" |
|||
{!../../../docs_src/dependencies/tutorial007.py!} |
|||
``` |
|||
|
|||
O valor gerado (yielded) é o que é injetado nas *operações de rota* e outras dependências. |
|||
|
|||
```Python hl_lines="4" |
|||
{!../../../docs_src/dependencies/tutorial007.py!} |
|||
``` |
|||
|
|||
O código após o `yield` é executado após a resposta ser entregue: |
|||
|
|||
```Python hl_lines="5-6" |
|||
{!../../../docs_src/dependencies/tutorial007.py!} |
|||
``` |
|||
|
|||
!!! tip "Dica" |
|||
Você pode usar funções assíncronas (`async`) ou funções comuns. |
|||
|
|||
O **FastAPI** saberá o que fazer com cada uma, da mesma forma que as dependências comuns. |
|||
|
|||
## Uma dependência com `yield` e `try` |
|||
|
|||
Se você utilizar um bloco `try` em uma dependência com `yield`, você irá capturar qualquer exceção que for lançada enquanto a dependência é utilizada. |
|||
|
|||
Por exemplo, se algum código em um certo momento no meio da operação, em outra dependência ou em uma *operação de rota*, fizer um "rollback" de uma transação de banco de dados ou causar qualquer outro erro, você irá capturar a exceção em sua dependência. |
|||
|
|||
Então, você pode procurar por essa exceção específica dentro da dependência com `except AlgumaExcecao`. |
|||
|
|||
Da mesma forma, você pode utilizar `finally` para garantir que os passos de saída são executados, com ou sem exceções. |
|||
|
|||
```python hl_lines="3 5" |
|||
{!../../../docs_src/dependencies/tutorial007.py!} |
|||
``` |
|||
|
|||
## Subdependências com `yield` |
|||
|
|||
Você pode ter subdependências e "árvores" de subdependências de qualquer tamanho e forma, e qualquer uma ou todas elas podem utilizar `yield`. |
|||
|
|||
O **FastAPI** garantirá que o "código de saída" em cada dependência com `yield` é executado na ordem correta. |
|||
|
|||
Por exemplo, `dependency_c` pode depender de `dependency_b`, e `dependency_b` depender de `dependency_a`: |
|||
|
|||
=== "python 3.9+" |
|||
|
|||
```python hl_lines="6 14 22" |
|||
{!> ../../../docs_src/dependencies/tutorial008_an_py39.py!} |
|||
``` |
|||
|
|||
=== "python 3.8+" |
|||
|
|||
```python hl_lines="5 13 21" |
|||
{!> ../../../docs_src/dependencies/tutorial008_an.py!} |
|||
``` |
|||
|
|||
=== "python 3.8+ non-annotated" |
|||
|
|||
!!! tip "Dica" |
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
```python hl_lines="4 12 20" |
|||
{!> ../../../docs_src/dependencies/tutorial008.py!} |
|||
``` |
|||
|
|||
E todas elas podem utilizar `yield`. |
|||
|
|||
Neste caso, `dependency_c` precisa que o valor de `dependency_b` (nomeada de `dep_b` aqui) continue disponível para executar seu código de saída. |
|||
|
|||
E, por outro lado, `dependency_b` precisa que o valor de `dependency_a` (nomeada de `dep_a`) continue disponível para executar seu código de saída. |
|||
|
|||
=== "python 3.9+" |
|||
|
|||
```python hl_lines="18-19 26-27" |
|||
{!> ../../../docs_src/dependencies/tutorial008_an_py39.py!} |
|||
``` |
|||
|
|||
=== "python 3.8+" |
|||
|
|||
```python hl_lines="17-18 25-26" |
|||
{!> ../../../docs_src/dependencies/tutorial008_an.py!} |
|||
``` |
|||
|
|||
=== "python 3.8+ non-annotated" |
|||
|
|||
!!! tip "Dica" |
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
```python hl_lines="16-17 24-25" |
|||
{!> ../../../docs_src/dependencies/tutorial008.py!} |
|||
``` |
|||
|
|||
Da mesma forma, você pode ter algumas dependências com `yield` e outras com `return` e ter uma relação de dependência entre algumas dos dois tipos. |
|||
|
|||
E você poderia ter uma única dependência que precisa de diversas outras dependências com `yield`, etc. |
|||
|
|||
Você pode ter qualquer combinação de dependências que você quiser. |
|||
|
|||
O **FastAPI** se encarrega de executá-las na ordem certa. |
|||
|
|||
!!! note "Detalhes Técnicos" |
|||
Tudo isso funciona graças aos <a href="https://docs.python.org/3/library/contextlib.html" class="external-link" target="_blank">gerenciadores de contexto</a> do Python. |
|||
|
|||
O **FastAPI** utiliza eles internamente para alcançar isso. |
|||
|
|||
## Dependências com `yield` e `httpexception` |
|||
|
|||
Você viu que dependências podem ser utilizadas com `yield` e podem incluir blocos `try` para capturar exceções. |
|||
|
|||
Da mesma forma, você pode lançar uma `httpexception` ou algo parecido no código de saída, após o `yield` |
|||
|
|||
!!! tip "Dica" |
|||
|
|||
Essa é uma técnica relativamente avançada, e na maioria dos casos você não precisa dela totalmente, já que você pode lançar exceções (incluindo `httpexception`) dentro do resto do código da sua aplicação, por exemplo, em uma *função de operação de rota*. |
|||
|
|||
Mas ela existe para ser utilizada caso você precise. 🤓 |
|||
|
|||
=== "python 3.9+" |
|||
|
|||
```python hl_lines="18-22 31" |
|||
{!> ../../../docs_src/dependencies/tutorial008b_an_py39.py!} |
|||
``` |
|||
|
|||
=== "python 3.8+" |
|||
|
|||
```python hl_lines="17-21 30" |
|||
{!> ../../../docs_src/dependencies/tutorial008b_an.py!} |
|||
``` |
|||
|
|||
=== "python 3.8+ non-annotated" |
|||
|
|||
!!! tip "Dica" |
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
```python hl_lines="16-20 29" |
|||
{!> ../../../docs_src/dependencies/tutorial008b.py!} |
|||
``` |
|||
|
|||
Uma alternativa que você pode utilizar para capturar exceções (e possivelmente lançar outra HTTPException) é criar um [Manipulador de Exceções Customizado](../handling-errors.md#instalando-manipuladores-de-excecoes-customizados){.internal-link target=_blank}. |
|||
|
|||
## Dependências com `yield` e `except` |
|||
|
|||
Se você capturar uma exceção com `except` em uma dependência que utilize `yield` e ela não for levantada novamente (ou uma nova exceção for levantada), o FastAPI não será capaz de identifcar que houve uma exceção, da mesma forma que aconteceria com Python puro: |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="15-16" |
|||
{!> ../../../docs_src/dependencies/tutorial008c_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.8+" |
|||
|
|||
```Python hl_lines="14-15" |
|||
{!> ../../../docs_src/dependencies/tutorial008c_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.8+ non-annotated" |
|||
|
|||
!!! tip "dica" |
|||
utilize a versão com `Annotated` se possível. |
|||
|
|||
```Python hl_lines="13-14" |
|||
{!> ../../../docs_src/dependencies/tutorial008c.py!} |
|||
``` |
|||
|
|||
Neste caso, o cliente irá ver uma resposta *HTTP 500 Internal Server Error* como deveria acontecer, já que não estamos levantando nenhuma `HTTPException` ou coisa parecida, mas o servidor **não terá nenhum log** ou qualquer outra indicação de qual foi o erro. 😱 |
|||
|
|||
### Sempre levante (`raise`) exceções em Dependências com `yield` e `except` |
|||
|
|||
Se você capturar uma exceção em uma dependência com `yield`, a menos que você esteja levantando outra `HTTPException` ou coisa parecida, você deveria relançar a exceção original. |
|||
|
|||
Você pode relançar a mesma exceção utilizando `raise`: |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="17" |
|||
{!> ../../../docs_src/dependencies/tutorial008d_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.8+" |
|||
|
|||
```Python hl_lines="16" |
|||
{!> ../../../docs_src/dependencies/tutorial008d_an.py!} |
|||
``` |
|||
|
|||
=== "python 3.8+ non-annotated" |
|||
|
|||
!!! tip "Dica" |
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
```Python hl_lines="15" |
|||
{!> ../../../docs_src/dependencies/tutorial008d.py!} |
|||
``` |
|||
|
|||
Agora o cliente irá receber a mesma resposta *HTTP 500 Internal Server Error*, mas o servidor terá nosso `InternalError` personalizado nos logs. 😎 |
|||
|
|||
## Execução de dependências com `yield` |
|||
|
|||
A sequência de execução é mais ou menos como esse diagrama. O tempo passa do topo para baixo. E cada coluna é uma das partes interagindo ou executando código. |
|||
|
|||
```mermaid |
|||
sequenceDiagram |
|||
|
|||
participant client as Cliente |
|||
participant handler as Manipulador de exceções |
|||
participant dep as Dep com yield |
|||
participant operation as Operação de Rota |
|||
participant tasks as Tarefas de Background |
|||
|
|||
Note over client,operation: pode lançar exceções, incluindo HTTPException |
|||
client ->> dep: Iniciar requisição |
|||
Note over dep: Executar código até o yield |
|||
opt lançar Exceção |
|||
dep -->> handler: lançar Exceção |
|||
handler -->> client: resposta de erro HTTP |
|||
end |
|||
dep ->> operation: Executar dependência, e.g. sessão de BD |
|||
opt raise |
|||
operation -->> dep: Lançar exceção (e.g. HTTPException) |
|||
opt handle |
|||
dep -->> dep: Pode capturar exceções, lançar uma nova HTTPException, lançar outras exceções |
|||
end |
|||
handler -->> client: resposta de erro HTTP |
|||
end |
|||
|
|||
operation ->> client: Retornar resposta ao cliente |
|||
Note over client,operation: Resposta já foi enviada, e não pode ser modificada |
|||
opt Tarefas |
|||
operation -->> tasks: Enviar tarefas de background |
|||
end |
|||
opt Lançar outra exceção |
|||
tasks -->> tasks: Manipula exceções no código da tarefa de background |
|||
end |
|||
``` |
|||
|
|||
!!! info "Informação" |
|||
Apenas **uma resposta** será enviada para o cliente. Ela pode ser uma das respostas de erro, ou então a resposta da *operação de rota*. |
|||
|
|||
Após uma dessas respostas ser enviada, nenhuma outra resposta pode ser enviada |
|||
|
|||
!!! tip "Dica" |
|||
Esse diagrama mostra `HttpException`, mas você pode levantar qualquer outra exceção que você capture em uma dependência com `yield` ou um [Manipulador de exceções personalizado](../handling-errors.md#instalando-manipuladores-de-excecoes-customizados){.internal-link target=_blank}. |
|||
|
|||
Se você lançar qualquer exceção, ela será passada para as dependências com yield, inlcuindo a `HTTPException`. Na maioria dos casos você vai querer relançar essa mesma exceção ou uma nova a partir da dependência com `yield` para garantir que ela seja tratada adequadamente. |
|||
|
|||
## Dependências com `yield`, `HTTPException`, `except` e Tarefas de Background |
|||
|
|||
!!! warning "Aviso" |
|||
Você provavelmente não precisa desses detalhes técnicos, você pode pular essa seção e continuar na próxima seção abaixo. |
|||
|
|||
Esses detalhes são úteis principalmente se você estiver usando uma versão do FastAPI anterior à 0.106.0 e utilizando recursos de dependências com `yield` em tarefas de background. |
|||
|
|||
### Dependências com `yield` e `except`, Detalhes Técnicos |
|||
|
|||
Antes do FastAPI 0.110.0, se você utilizasse uma dependência com `yield`, e então capturasse uma dependência com `except` nessa dependência, caso a exceção não fosse relançada, ela era automaticamente lançada para qualquer manipulador de exceções ou o manipulador de erros interno do servidor. |
|||
|
|||
Isso foi modificado na versão 0.110.0 para consertar o consumo de memória não controlado das exceções relançadas automaticamente sem um manipulador (erros internos do servidor), e para manter o comportamento consistente com o código Python tradicional. |
|||
|
|||
### Tarefas de Background e Dependências com `yield`, Detalhes Técnicos |
|||
|
|||
Antes do FastAPI 0.106.0, levantar exceções após um `yield` não era possível, o código de saída nas dependências com `yield` era executado *após* a resposta ser enviada, então os [Manipuladores de Exceções](../handling-errors.md#instalando-manipuladores-de-excecoes-customizados){.internal-link target=_blank} já teriam executado. |
|||
|
|||
Isso foi implementado dessa forma principalmente para permitir que os mesmos objetos fornecidos ("yielded") pelas dependências dentro de tarefas de background fossem reutilizados, por que o código de saída era executado antes das tarefas de background serem finalizadas. |
|||
|
|||
Ainda assim, como isso exigiria esperar que a resposta navegasse pela rede enquanto mantia ativo um recurso desnecessário na dependência com yield (por exemplo, uma conexão com banco de dados), isso mudou na versão 0.106.0 do FastAPI. |
|||
|
|||
!!! tip "Dica" |
|||
|
|||
Adicionalmente, uma tarefa de background é, normalmente, um conjunto de lógicas independentes que devem ser manipuladas separadamente, com seus próprios recursos (e.g. sua própria conexão com banco de dados). |
|||
|
|||
Então, dessa forma você provavelmente terá um código mais limpo. |
|||
|
|||
Se você costumava depender desse comportamento, agora você precisa criar os recursos para uma tarefa de background dentro dela mesma, e usar internamente apenas dados que não dependam de recursos de dependências com `yield`. |
|||
|
|||
Por exemplo, em vez de utilizar a mesma sessão do banco de dados, você criaria uma nova sessão dentro da tarefa de background, e você obteria os objetos do banco de dados utilizando essa nova sessão. E então, em vez de passar o objeto obtido do banco de dados como um parâmetro para a função da tarefa de background, você passaria o ID desse objeto e buscaria ele novamente dentro da função da tarefa de background. |
|||
|
|||
## Gerenciadores de contexto |
|||
|
|||
### O que são gerenciadores de contexto |
|||
|
|||
"Gerenciadores de Contexto" são qualquer um dos objetos Python que podem ser utilizados com a declaração `with`. |
|||
|
|||
Por exemplo, <a href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files" class="external-link" target="_blank">você pode utilizar `with` para ler um arquivo</a>: |
|||
|
|||
```Python |
|||
with open("./somefile.txt") as f: |
|||
contents = f.read() |
|||
print(contents) |
|||
``` |
|||
|
|||
Por baixo dos panos, o código `open("./somefile.txt")` cria um objeto que é chamado de "Gerenciador de Contexto". |
|||
|
|||
Quando o bloco `with` finaliza, ele se certifica de fechar o arquivo, mesmo que tenha ocorrido alguma exceção. |
|||
|
|||
Quando você cria uma dependência com `yield`, o **FastAPI** irá criar um gerenciador de contexto internamente para ela, e combiná-lo com algumas outras ferramentas relacionadas. |
|||
|
|||
### Utilizando gerenciadores de contexto em dependências com `yield` |
|||
|
|||
!!! warning "Aviso" |
|||
Isso é uma ideia mais ou menos "avançada". |
|||
|
|||
Se você está apenas iniciando com o **FastAPI** você pode querer pular isso por enquanto. |
|||
|
|||
Em python, você pode criar Gerenciadores de Contexto ao <a href="https://docs.python.org/3/reference/datamodel.html#context-managers" class="external-link" target="_blank"> criar uma classe com dois métodos: `__enter__()` e `__exit__()`</a>. |
|||
|
|||
Você também pode usá-los dentro de dependências com `yield` do **FastAPI** ao utilizar `with` ou `async with` dentro da função da dependência: |
|||
|
|||
```Python hl_lines="1-9 13" |
|||
{!../../../docs_src/dependencies/tutorial010.py!} |
|||
``` |
|||
|
|||
!!! tip "Dica" |
|||
Outra forma de criar um gerenciador de contexto é utilizando: |
|||
|
|||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> ou |
|||
|
|||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a> |
|||
|
|||
Para decorar uma função com um único `yield`. |
|||
|
|||
Isso é o que o **FastAPI** usa internamente para dependências com `yield`. |
|||
|
|||
Mas você não precisa usar esses decoradores para as dependências do FastAPI (e você não deveria). |
|||
|
|||
O FastAPI irá fazer isso para você internamente. |
Loading…
Reference in new issue