Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR

### Corrigido

- Nos providers `claude_code` e `codex`, o status HTTP 408 ou 409 dá `ProviderTransientError`, e a linha é repetida com backoff, como no erro de SDK que chega pelo LangChain; no 408, o aviso sai com a caixa de timeout. Antes, os dois status davam `ProviderError` definitivo, e a linha ficava com erro na primeira falha (#274).
- A caixa genérica de erro (`ERRO NO PROCESSAMENTO`) mostra a mensagem em até três linhas, terminada em reticências quando passa disso, e a moldura fica alinhada nas linhas do tipo e dos detalhes. Antes, a mensagem era cortada em 66 caracteres sem aviso, e no `claude_code` o corte caía dentro do prefixo `Claude Code SDK retornou erro (subtipo, status N): `, sem mostrar nada do texto do SDK (#275).
- No provider `claude_code`, o `api_error_status` que o SDK entrega como texto de dígitos ou float inteiro, como `"401"` ou `401.0`, é classificado como o status inteiro: `"401"` dá a caixa de autenticação sem nova tentativa, e `"429"` reduz o paralelismo. `True`, `False` e os demais valores contam como status ausente; antes, `True` passava como status e virava `ProviderError` definitivo com `status_code=True` (#276).


## [0.11.1] - 2026-10-29

Expand Down
13 changes: 11 additions & 2 deletions src/dataframeit/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1841,9 +1841,18 @@ def dataframeit( # noqa: C901, PLR0912, PLR0913, PLR0915, PLR0917 (API pública
"(marcadas em _error_details); repetir a chamada com reprocess_columns "
"refaz todas as linhas, inclusive as já reprocessadas"
)
# resume=True sobre a saída só retoma quando a saída traz a coluna de
# texto, e só a saída de entrada DataFrame a traz; para list, dict e
# Series a via de retomada é o checkpoint.
output_has_text = conversion_info.original_type in (
ORIGINAL_TYPE_PANDAS_DF,
ORIGINAL_TYPE_POLARS_DF,
)
if checkpoint is not None:
how = "rode de novo com o mesmo checkpoint_path, ou com resume=True sobre esta saída"
elif conversion_info.original_type in (ORIGINAL_TYPE_PANDAS_DF, ORIGINAL_TYPE_POLARS_DF):
how = "rode de novo com o mesmo checkpoint_path"
if output_has_text:
how += ", ou com resume=True sobre esta saída"
elif output_has_text:
how = "rode de novo com resume=True sobre esta saída"
else:
how = (
Expand Down
43 changes: 43 additions & 0 deletions tests/test_interrupcao.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Falha que impede as linhas seguintes interrompe a execução sem gastá-las."""

import re
import signal
import sys
import threading
Expand Down Expand Up @@ -185,6 +186,48 @@ def test_aviso_diz_como_retomar_conforme_a_entrada(entrada, trecho):
_roda(entrada(), llm)


_SO_CHECKPOINT = "rode de novo com o mesmo checkpoint_path."
_CHECKPOINT_OU_RESUME = (
"rode de novo com o mesmo checkpoint_path, ou com resume=True sobre esta saída."
)
_SO_RESUME = "rode de novo com resume=True sobre esta saída."
_SEM_VIA = (
"a entrada não é um DataFrame e a saída não traz o texto, então só um "
"checkpoint_path permite retomar."
)


@pytest.mark.parametrize(
("entrada", "com_checkpoint", "via"),
[
(lambda: ["a", "b", "c"], True, _SO_CHECKPOINT),
(lambda: {"k1": "a", "k2": "b", "k3": "c"}, True, _SO_CHECKPOINT),
(lambda: pd.DataFrame({"texto": ["a", "b", "c"]}), True, _CHECKPOINT_OU_RESUME),
(lambda: ["a", "b", "c"], False, _SEM_VIA),
(lambda: {"k1": "a", "k2": "b", "k3": "c"}, False, _SEM_VIA),
(lambda: pd.DataFrame({"texto": ["a", "b", "c"]}), False, _SO_RESUME),
],
ids=[
"lista-com-checkpoint",
"dict-com-checkpoint",
"dataframe-com-checkpoint",
"lista-sem-checkpoint",
"dict-sem-checkpoint",
"dataframe-sem-checkpoint",
],
)
def test_aviso_so_oferece_resume_sobre_a_saida_quando_ela_traz_o_texto(
entrada, com_checkpoint, via, tmp_path
):
# O texto inteiro da via de retomada é fixado, ancorado no fim da mensagem,
# para que nenhuma via a mais apareça.
_, llm = _llm_que_esgota(1, ProviderUsageLimitError("usage limit"))
extra = {"checkpoint_path": tmp_path / "c.csv", "batch_size": 1} if com_checkpoint else {}

with pytest.warns(UserWarning, match=re.escape(f"Para retomar, {via}") + "$"):
_roda(entrada(), llm, **extra)


def test_aviso_com_checkpoint_manda_usar_o_mesmo_arquivo(tmp_path):
_, llm = _llm_que_esgota(1, ProviderUsageLimitError("usage limit"))

Expand Down
Loading