Skip to content

feat(filter): apply comment limit after filtering - #2

Open
gabicastrum wants to merge 2 commits into
othonhugo:mainfrom
gabicastrum:feat/limit-lazy
Open

feat(filter): apply comment limit after filtering#2
gabicastrum wants to merge 2 commits into
othonhugo:mainfrom
gabicastrum:feat/limit-lazy

Conversation

@gabicastrum

@gabicastrum gabicastrum commented Jul 21, 2026

Copy link
Copy Markdown

MR: Aplicar --limit após o filtro, não antes

Contexto / Problema

Hoje, ao rodar:

prawler comments /brdev \
  --filter "body ~= \b(?-i:IA)\b|\bChatGPT\b|\bClaude\s*Code\b|\bintelig[êe]ncia\s+artificial\b" \
  --format table \
  --limit 20

a expectativa é receber 20 comentários que atendem ao filtro. O que acontece hoje é diferente: limit é passado direto para o crawler, que o repassa para a chamada do PRAW. Isso corta o stream antes do filtro rodar. Resultado: buscamos 20 comentários brutos, filtramos esse subconjunto pequeno, e o total final quase sempre fica abaixo do que foi pedido.

A causa raiz é um único parâmetro (limit) sendo usado para dois papéis distintos: quantidade buscada na fonte e quantidade desejada após aplicar regra de negócio. Este MR separa esses dois papéis.

Solução

  1. O crawler não corta mais a busca. SubmissionCommentConfig/UserCommentConfig agora sempre recebem limit=None, o crawler entrega todo o stream lazy disponível na fonte, sem opinar sobre quantidade final.
  2. O corte passa a acontecer depois do pipeline de filtros, via itertools.islice(filtered_records, limit). Como tudo é Iterator, o islice só continua "puxando" registros da fonte até acumular limit itens já aprovados pelo filtro, ou até a fonte se esgotar.
  3. Teto de segurança por tempo. _with_deadline encerra o consumo após COMMENTS_MAX_SECONDS (300s), devolvendo o que já foi coletado até ali, sem travar indefinidamente em filtros muito restritivos.

Mudanças por arquivo

  • src/prawler/cli/commands/comments.py

    • _with_deadline(records, max_seconds): novo estágio consumidor que interrompe a iteração ao atingir o deadline.
    • from_user/from_submission agora chamados com limit=None, delegando o corte de quantidade para depois do pipeline.
    • Pipeline passa a ser encadeado como: crawler → filtros → deadline → islice(limit) → formatter → sink.
  • docs/usage/commands.md

    • Documenta explicitamente o novo comportamento: --limit filtra e só então corta; para uma única submission, o resultado ainda depende de aquele post ter comentários suficientes que batam no critério.
  • tests/test__cli__commands__comments.py (novo)

    • test_comments_limit_applies_after_filter: crawler fake devolve 4 comentários (1 rejeitado, 3 aprovados), --limit 2 e filtro body ~= keep valida que o corte respeita o filtro e que o crawler foi chamado com limit=None.
    • test_comments_user_source_is_unbounded: garante que a busca por u/username também segue sem teto na fonte.
  • tests/test__pipeline__filters.py (novo)

    • Valida o regex de detecção de IA contra casos ambíguos: "ia" como verbo (não deve passar), "IA" maiúsculo isolado, variações de caixa/acentuação em "inteligência artificial", e falsos positivos por substring parcial.

Como testar manualmente

# Filtro restritivo:  deve retornar menos que o limit se a comunidade
# não tiver comentários suficientes, sem travar além de 300s
prawler comments /brdev \
  --filter "body ~= \b(?-i:IA)\b" \
  --limit 500 \
  --format table

# Caso normal: deve continuar buscando até achar exatamente `limit` aprovados
prawler comments /datascience \
  --filter "body ~= \b(?-i:IA)\b|\bChatGPT\b" \
  --limit 20 \
  --format table

@othonhugo othonhugo self-assigned this Jul 23, 2026

@othonhugo othonhugo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A implementação ficou muito boa! Deixei só alguns comentários com sugestões que acho que podem melhorar a legibilidade e a organização do código. Ainda preciso testar o comportamento em runtime pra validar tudo, mas a implementação parece bem consistente :)

Comment on lines +15 to +26
COMMENTS_MAX_SECONDS = 300


def _with_deadline(records: Iterator[dict[str, object]], max_seconds: int) -> Iterator[dict[str, object]]:
deadline = time.monotonic() + max_seconds

for record in records:
if time.monotonic() >= deadline:
return

yield record

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boa função! Acho que também faz sentido criar um novo módulo utils e movê-la pra lá pra poder ser reusada em outras partes do código.

Além disso, seria melhor ainda se puder user um tipo genérico no lugar de object, preservando o mesmo tipo de entrada e saída da função.

Já a constante COMMENTS_MAX_SECONDS poderia continuar neste módulo e ser passada como argumento pro utilitário.

Exemplo:

# src/prawler/utils/iterators.py
from typing import TypeVar

T = TypeVar("T")


def with_deadline(records: Iterator[T], max_seconds: int) -> Iterator[T]:
    deadline = time.monotonic() + max_seconds

    for record in records:
        if time.monotonic() >= deadline:
            return

        yield record

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acho que vale ajustar o nome dos arquivos de teste para seguir uma convenção mais comum, como test_cli_commands_comments.py. A ideia é usar um único _ para separar os diretórios do caminho do módulo testado, em vez de __.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outro ponto é que o diretório tests geralmente fica na raiz do repositório, no mesmo nível de src e docs. Acho que vale a pena seguir essa convenção e alinhar a estrutura do projeto com o que é mais comum na comunidade.

@@ -0,0 +1,22 @@
from prawler.pipeline.filters import make_filter_stage

def test_filtro_ia_ignora_verbo_ir():

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acho que vale renomear test_filtro_ia_ignora_verbo_ir para inglês e para descrever o comportamento que está sendo testado. Como o foco é validar o filtro usando um operador de regex (~=), algo como test_filter_regex_operator_matches_expected_records (ou outro nome equivalente) tornaria a intenção do teste mais explícita.

@othonhugo othonhugo added enhancement New feature or request good first issue Good for newcomers labels Jul 23, 2026
@othonhugo othonhugo changed the title feat: Apply comment limit after filtering feat(filter): apply comment limit after filtering Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request good first issue Good for newcomers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants