diff --git a/aulas/07.md b/aulas/07.md index 7fb3f9d9..e18ccb20 100644 --- a/aulas/07.md +++ b/aulas/07.md @@ -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" @@ -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 diff --git a/aulas/09.md b/aulas/09.md index 30ea3972..9d2b5351 100644 --- a/aulas/09.md +++ b/aulas/09.md @@ -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. diff --git a/aulas/assets/09/swagger_queryparams.png b/aulas/assets/09/swagger_queryparams.png new file mode 100644 index 00000000..348b047c Binary files /dev/null and b/aulas/assets/09/swagger_queryparams.png differ