From bbbfbcb00dc9d62292fbff17a0c6de2fabf3ef31 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 29 Oct 2026 17:31:48 -0300 Subject: [PATCH 1/2] =?UTF-8?q?Aviso=20de=20interrup=C3=A7=C3=A3o=20omite?= =?UTF-8?q?=20resume=3DTrue=20quando=20a=20entrada=20n=C3=A3o=20=C3=A9=20D?= =?UTF-8?q?ataFrame?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Com checkpoint_path, o aviso oferecia resume=True sobre a saída também para entrada list e dict, cuja saída não traz o texto. A via do resume=True só aparece quando a entrada é DataFrame, pandas ou polars, como já acontecia no aviso sem checkpoint_path (#277). --- CHANGELOG.md | 4 ++++ src/dataframeit/core.py | 13 +++++++++-- tests/test_interrupcao.py | 45 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9372032b..8a989ed2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR ## [Unreleased] +### Corrigido + +- Com `checkpoint_path` e entrada que não é DataFrame, como `list` ou `dict`, o aviso de execução interrompida manda retomar só pelo mesmo `checkpoint_path`, sem oferecer `resume=True` sobre a saída, que não traz o texto e levanta `ValueError` (#277). + ## [0.11.1] - 2026-10-29 ### Corrigido diff --git a/src/dataframeit/core.py b/src/dataframeit/core.py index 029e5ff3..12a11082 100644 --- a/src/dataframeit/core.py +++ b/src/dataframeit/core.py @@ -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 = ( diff --git a/tests/test_interrupcao.py b/tests/test_interrupcao.py index 507c9362..4c4db5c6 100644 --- a/tests/test_interrupcao.py +++ b/tests/test_interrupcao.py @@ -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 @@ -185,6 +186,50 @@ 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 +): + # resume=True sobre a saída só funciona quando a saída traz a coluna de + # texto, o que acontece só com entrada DataFrame. 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")) From d5da258dd576c8adf5930e48969c1a0fe6f471cf Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 29 Oct 2026 17:42:54 -0300 Subject: [PATCH 2/2] =?UTF-8?q?Coment=C3=A1rio=20do=20teste=20do=20aviso?= =?UTF-8?q?=20de=20retomada=20fica=20s=C3=B3=20com=20o=20que=20o=20nome=20?= =?UTF-8?q?n=C3=A3o=20diz?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_interrupcao.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_interrupcao.py b/tests/test_interrupcao.py index 4c4db5c6..a8970009 100644 --- a/tests/test_interrupcao.py +++ b/tests/test_interrupcao.py @@ -219,10 +219,8 @@ def test_aviso_diz_como_retomar_conforme_a_entrada(entrada, trecho): def test_aviso_so_oferece_resume_sobre_a_saida_quando_ela_traz_o_texto( entrada, com_checkpoint, via, tmp_path ): - # resume=True sobre a saída só funciona quando a saída traz a coluna de - # texto, o que acontece só com entrada DataFrame. O texto inteiro da via de - # retomada é fixado, ancorado no fim da mensagem, para que nenhuma via a - # mais apareça. + # 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 {}