Ir para o conteúdo

Exercícios da aula 09

Exercício 01

Adicione os campos created_at e updated_at na tabela Todo - Eles devem ser init=False - Deve usar func.now() para criação - O campo updated_at deve ter onupdate

Solução

Devem ser adicionados os dois campos ao modelo Todo:

fast_zero/models.py
@table_registry.mapped_as_dataclass
class Todo:
    __tablename__ = 'todos'

    id: Mapped[int] = mapped_column(init=False, primary_key=True)
    title: Mapped[str]
    description: Mapped[str]
    state: Mapped[TodoState]

    user_id: Mapped[int] = mapped_column(ForeignKey('users.id'))

    user: Mapped[User] = relationship(init=False, back_populates='todos')

    # Exercício 01
    created_at: Mapped[datetime] = mapped_column(
        init=False, server_default=func.now()
    )
    updated_at: Mapped[datetime] = mapped_column(
        init=False, server_default=func.now(), onupdate=func.now()
    )

Exercício 02

Criar uma migração para que os novos campos sejam versionados e também aplicar a migração

Solução

Se executarmos a migração com o primeiro exercício resolvido, teremos algo como:

$ Execução no terminal!
alembic revision --autogenerate -m "Adicionando created_at e updated_at na tabela de todos"
^[[AINFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added column 'todos.created_at'
INFO  [alembic.autogenerate.compare] Detected added column 'todos.updated_at'
INFO  [alembic.autogenerate.compare] Detected added column 'users.updated_at'
  Generating /home/dunossauro/git/fastapi-do-
  zero/codigo_das_aulas/09/migrations/versions/bd7cea4a4773_adicionando_created_at_e_updated_at_na_.py ...  done

Gerando a seguinte migração:

"""Adicionando created_at e updated_at na tabela de todos

Revision ID: bd7cea4a4773
Revises: 3a79a86c9e4a
Create Date: 2024-10-05 01:11:38.100051

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = 'bd7cea4a4773'
down_revision: Union[str, None] = '3a79a86c9e4a'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('todos', sa.Column('created_at', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False))
    op.add_column('todos', sa.Column('updated_at', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False))
    # ### end Alembic commands ###


def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('todos', 'updated_at')
    op.drop_column('todos', 'created_at')
    # ### end Alembic commands ###

Exercício 03

Adicionar os campos created_at e updated_at no schema de saída dos endpoints. Para que esse valores sejam retornados na API.

Solução

Para adicionar os campos é necessário somente a adição dos mesmos no schema:

fast_zero/schemas.py
from datetime import datetime
# ...


class TodoPublic(TodoSchema):
    id: int
    created_at: datetime
    updated_at: datetime

A adaptação do teste, para validar o tempo, pode usar o evento de mock_db_time. Como o pydantic converte o resultado para json, ele transforma a data no formato iso. Isso deve ser levado em conta na comparação:

tests/test_todos.py
from http import HTTPStatus

from fast_zero.models import Todo, TodoState
from tests.factories import TodoFactory


def test_create_todo(client, token, mock_db_time):
    with mock_db_time(model=Todo) as time:
        response = client.post(
            '/todos/',
            headers={'Authorization': f'Bearer {token}'},
            json={
                'title': 'Test todo',
                'description': 'Test todo description',
                'state': 'draft',
            },
        )

    assert response.json() == {
        'id': 1,
        'title': 'Test todo',
        'description': 'Test todo description',
        'state': 'draft',
        'created_at': time.isoformat(),
        'updated_at': time.isoformat()
    }

Exercício 04

Crie um teste para o endpoint de busca (GET) que valide todos os campos contidos no Todo de resposta. Até o momento, todas as validações foram feitas pelo tamanho do resultado de todos.

Solução

Esse exercício é um pouco mais trabalhoso que os demais. Vamos dividir ele em etapas:

  1. Devemos ter o tempo determinístico (mock_db_time) para poder validar o json
  2. Devemos criar um todo com dados aleatórios (TodoFactory)
  3. Devemos ter um token e um usuário criado

No final das contas, algo parecido (não necessariamente idêntico) a isso:

def test_list_todos_should_return_all_expected_fields__exercicio(
    session, client, user, token, mock_db_time
):
    with mock_db_time(model=Todo) as time:
        todo = TodoFactory.create(user_id=user.id)
        session.add(todo)
        session.commit()

    session.refresh(todo)
    response = client.get(
        '/todos/',
        headers={'Authorization': f'Bearer {token}'},
    )

    assert response.json()['todos'] == [{
        'created_at': time.isoformat(),
        'updated_at': time.isoformat(),
        'description': todo.description,
        'id': todo.id,
        'state': todo.state,
        'title': todo.title,
    }]