Skip to content

Commit

Permalink
Adicionando suporte a 0.115 na aula 09
Browse files Browse the repository at this point in the history
  • Loading branch information
dunossauro committed Sep 30, 2024
1 parent 5c8e6ab commit 7e9cfa4
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 25 deletions.
8 changes: 6 additions & 2 deletions aulas/07.md
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,9 @@ def read_users(
return {'users': users}
```

Embora isso não seja efetivamente um problema, parâmetros de `offset` e `limit` são bastante genéricos e podem ser usados em qualquer endpoint que precisar de paginação. Uma boa pratica de organização é seria um modelo do pydantic especializado em filtros, como:
Embora isso não seja efetivamente um problema, parâmetros de `offset` e `limit` são bastante genéricos e podem ser usados em qualquer endpoint que precisar de paginação.

Uma boa pratica de organização é seria um modelo do pydantic especializado em filtros, como:


```py title="fast_zero/schemas.py"
Expand All @@ -571,7 +573,9 @@ Dessa forma, qualquer endpoint que precisar paginar resultados podem se benefici

### Implementação de querystrings via Pydantic

Com esse modelo, podemos importar o objeto `Query` do Fastapi no arquivo de rotas e passar o modelo de filtro via o tipo `Annotated` em conjunto:
Uma das formas de remover a declaração de todos os parâmetros explicitamente da query no endpoint é usar nosso modelo com o objeto `Query` do FastAPI.

Dessa forma podemos anotar o modelo do pydantic junto o objeto `Query`. Fazendo com que ele se torne um filtro:

```py title="fast_zero/routers/users.py" hl_lines="1 6 16"
from fastapi import APIRouter, Depends, HTTPException, Query
Expand Down
70 changes: 47 additions & 23 deletions aulas/09.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,61 +341,85 @@ Agora que criamos a nossa migração e temos o endpoint de criação de Todos, t

Algumas coisas adicionais e que podem ser importantes na hora de recuperar as tarefas é fazer um filtro de busca. Em alguns momentos queremos buscar uma tarefa por título, em outro momento por descrição, às vezes só pelo estado. Por exemplo, somente tarefas concluídas.

Para fazer isso, podemos contar com um recurso do FastAPI chamado `Query`. A `Query` permite que definamos parâmetros específicos na URL, que podem ser utilizados para filtrar os resultados retornados pelo endpoint. Isso é feito através da inclusão de parâmetros como query strings na URL, interpretados pelo FastAPI para ajustar a consulta ao banco de dados.

Por exemplo, uma query string simples pode ser: `todos/?title="batatinha"`.

Uma característica importante das queries do FastAPI é que podemos juntar mais de um atributo em uma busca. Por exemplo, podemos buscar somente as tarefas a fazer que contenham no título "trabalho". Dessa forma, temos um endpoint mais eficiente, já que podemos realizar buscas complexas e refinadas com uma única chamada.
Uma característica importante das queries é que podemos juntar mais de um atributo em uma busca. Por exemplo, podemos buscar somente as tarefas a fazer que contenham no título "trabalho". Dessa forma, temos um endpoint mais eficiente, já que podemos realizar buscas complexas e refinadas com uma única chamada.

A combinação poderia ser algo como: `todos/?title="batatinha"&status=todo`.

A combinação de diferentes parâmetros de query não só torna o endpoint mais flexível, mas também permite que os usuários obtenham os dados de que precisam de maneira mais rápida e conveniente. Isso contribui para uma melhor experiência do usuário e otimiza a interação com o banco de dados.

O código a seguir ilustra como o endpoint de listagem é definido utilizando a `Query`:
### O modelo da query

Como agora temos vários parâmetros de query como `title`, `description` e `state`, podemos criar um modelo como esse:

```py title="fast_zero/schemas.py"
class FilterTodo(FilterPage):
title: str | None = None
description: str | None = None
state: TodoState | None = None
```

Uma coisa interessante de observar nesse modelo é que ele usa `FilterPage` como base, para que além dos campos propostos, tenhamos o `limit` e `offset` também.

A definição de `state` tem um comportamento bastante interessante na documentação, gerando uma caixa de seleção para garantir que o valor correto seja enviado.

![Swagger mostrando os queryparams](assets/09/swagger_queryparams.png){: .center .shadow }

### Implementação do endpoint

Agora, com o modelo em mãos, podemos escrever nosso endpoint de listagem que leva em consideração todos os filtros possíveis na hora de fazer a busca:

```python title="fast_zero/routers/todos.py"
from typing import Annotated
# ...
from fastapi import APIRouter, Depends, Query
from sqlalchemy import select
# ...
from fast_zero.schemas import TodoList, TodoPublic, TodoSchema
from fast_zero.schemas import (
FilterTodo,
Message,
TodoList,
TodoPublic,
TodoSchema,
TodoUpdate,
)

# ...

@router.get('/', response_model=TodoList)
def list_todos( # noqa (1)
def list_todos(
session: Session,
user: CurrentUser,
title: str = Query(None),
description: str = Query(None),
state: str = Query(None),
offset: int = Query(None),
limit: int = Query(None),
todo_filter: Annotated[FilterTodo, Query()],
):
query = select(Todo).where(Todo.user_id == user.id)

if title:
query = query.filter(Todo.title.contains(title))
if todo_filter.title:
query = query.filter(Todo.title.contains(todo_filter.title))

if description:
query = query.filter(Todo.description.contains(description))
if todo_filter.description:
query = query.filter(
Todo.description.contains(todo_filter.description)
)

if state:
query = query.filter(Todo.state == state)
if todo_filter.state:
query = query.filter(Todo.state == todo_filter.state)

todos = session.scalars(query.offset(offset).limit(limit)).all()
todos = session.scalars(
query.offset(todo_filter.offset).limit(todo_filter.limit)
).all()

return {'todos': todos}
```

1. o código `noqa` significa que queremos ignorar essa definição durante a análise estática. Não é considerada uma boa prática de código ter uma função que recebe mais de 5 argumentos pela regra `R0913` do pylint. Nesse caso, porém, temos uma particularidade do FastAPI que recebe cada parâmetro de url por um argumento de função. Como queremos ignorar a checagem somente nessa definição, usamos o comentário `# noqa`

Essa abordagem equilibra a flexibilidade e a eficiência, tornando o endpoint capaz de atender a uma variedade de necessidades de negócio. Utilizando os recursos do FastAPI, conseguimos implementar uma solução robusta e fácil de manter, que será testada posteriormente para garantir sua funcionalidade e integridade.

No código acima, estamos utilizando filtros do SQLAlchemy, uma biblioteca ORM (Object-Relational Mapping) do Python, para adicionar condições à nossa consulta. Esses filtros correspondem aos parâmetros que o usuário pode passar na URL.

- `Todo.title.contains(title)`: verifica se o título da tarefa contém a string fornecida.
- `Todo.description.contains(description)`: verifica se a descrição da tarefa contém a string fornecida.
- `Todo.state == state`: compara o estado da tarefa com o valor fornecido.
- `Todo.title.contains(todo_filter.title)`: verifica se o título da tarefa contém a string fornecida.
- `Todo.description.contains(todo_filter.description)`: verifica se a descrição da tarefa contém a string fornecida.
- `Todo.state == todo_filter.state`: compara o estado da tarefa com o valor fornecido.

Essas condições são traduzidas em cláusulas SQL pelo SQLAlchemy, permitindo que o banco de dados filtre os resultados de acordo com os critérios especificados pelo usuário. Essa integração entre FastAPI e SQLAlchemy torna o processo de filtragem eficiente e a codificação mais expressiva e clara.

Expand Down
Binary file added aulas/assets/09/swagger_queryparams.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 7e9cfa4

Please sign in to comment.