Skip to content

Saída polars: pânico do polars na conversão final vira coluna Object - #236

Merged
bdcdo merged 1 commit into
mainfrom
fix/polars-panico-na-saida
Oct 14, 2026
Merged

bdcdo merged 1 commit into
mainfrom
fix/polars-panico-na-saida

Conversation

@bdcdo

@bdcdo bdcdo commented Oct 14, 2026

Copy link
Copy Markdown
Owner

O quê

Com entrada polars, um campo Decimal com valor acima de 38 dígitos derrubava a saída depois de todas as chamadas ao provider. O pyarrow entrega o valor em 256 bits, o polars entra em pânico ao lê-lo, e o PanicException, que herda de BaseException, passava pelo except Exception de from_pandas. Com entrada pandas, o mesmo modelo devolvia a coluna.

Como

O except de from_pandas passa a capturar também pl.exceptions.PanicException, presente do polars 1.0.0 ao 2.0.0, e a coluna sai Object com aviso, como as demais que a conversão recusa. BaseException fica fora, para que o KeyboardInterrupt continue subindo. O comentário do try descreve o que o código captura agora.

O mesmo except cobre o pânico do polars 1.6.0 a 1.9.0 com {} ao lado de None num campo Any. No campo Optional[dict[str, int]], citado na issue, esse pânico já não acontece: desde o #232 o campo dict sai em texto JSON.

Testes

  • Caso decimal_acima_de_38_digitos no parametrizado dos campos que o polars não tipa: reprova na main com PanicException e passa aqui.
  • test_interrupcao_na_conversao_da_saida_polars_sobe_em_vez_de_virar_object: o KeyboardInterrupt na conversão de uma coluna sobe até o chamador.

Closes #230

Com entrada polars, um campo Decimal com valor acima de 38 dígitos derrubava a saída depois de todas as chamadas: o pyarrow entrega o valor em 256 bits, o polars entra em pânico ao lê-lo, e o PanicException, que herda de BaseException, passava pelo except Exception de from_pandas. O except passa a capturar também pl.exceptions.PanicException, e a coluna sai Object com aviso, como as demais que o polars recusa. BaseException continua fora, para que o KeyboardInterrupt suba.

Closes #230
@bdcdo

bdcdo commented Oct 14, 2026

Copy link
Copy Markdown
Owner Author

Evidência

O caso da issue, na main e no branch

repro.py é o trecho da issue, com os avisos gravados e a saída impressa:

import warnings
from decimal import Decimal
from unittest.mock import patch

import polars as pl
from pydantic import BaseModel

from dataframeit import dataframeit


class M(BaseModel):
    total: Decimal


respostas = iter([Decimal("1.5"), Decimal("1E+40"), Decimal("2")])
with patch("dataframeit.core.call_langchain", side_effect=lambda *a, **k: {"data": {"total": next(respostas)}, "usage": None}), \
     patch("dataframeit.core.validate_provider_dependencies"), \
     warnings.catch_warnings(record=True) as avisos:
    warnings.simplefilter("always")
    saida = dataframeit(pl.DataFrame({"texto": ["a", "b", "c"]}), questions=M, prompt="{texto}", track_tokens=False)
print(saida.schema)
print(saida["total"].to_list())
print([str(a.message) for a in avisos if "Object" in str(a.message)])

Na main (c6e99a10), polars 2.0.0, Python 3.13, uv run python repro.py, código de saída 1:

thread '<unnamed>' panicked at crates/polars-arrow/src/ffi/array.rs:28:33:
operator does not support primitive `Int256`
...
    pys = PySeries.from_arrow(name, array)
pyo3_runtime.PanicException: operator does not support primitive `Int256`

No branch, mesmo ambiente, código de saída 0:

thread '<unnamed>' (737347) panicked at crates/polars-arrow/src/ffi/array.rs:28:33:
operator does not support primitive `Int256`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Schema({'texto': String, 'total': Object})
[Decimal('1.5'), Decimal('1E+40'), Decimal('2')]
["As colunas ['total'] têm valores de tipos que o polars não reúne numa coluna tipada e saem com o tipo Object, com os objetos Python que o modelo devolveu, exceto o Enum fora de lista ou dict, que sai pelo valor. Converta-as antes de gravar em arquivo."]

A mensagem thread ... panicked continua no stderr: é o Rust que a escreve antes de a exceção chegar ao Python. No polars 1.0.0 (job lowest-direct), o mesmo script dá o mesmo resultado: PanicException na main, coluna Object com os três valores e o aviso no branch.

Casos

Caso Esperado main Branch
Decimal com 1E+40, polars 2.0.0 coluna Object com os valores e aviso PanicException Object, [Decimal('1.5'), Decimal('1E+40'), Decimal('2')], aviso
O mesmo, polars 1.0.0 idem PanicException idem
Optional[dict[str, int]] com {} e None, polars 1.9.0 sem pânico String, ['{}', None], sem aviso idem; o campo dict já sai em texto JSON desde o #232 e não chega ao pânico
Any com {} e None, polars 1.9.0 coluna Object com aviso PanicException (invalid struct) Object, [{}, None], aviso
KeyboardInterrupt na conversão de uma coluna sobe até o chamador sobe sobe

Testes

Novos em tests/test_entrada_e_saida.py:

  • decimal_acima_de_38_digitos, caso novo do parametrizado test_campo_que_o_arrow_nao_tipa_sai_como_object_com_os_valores_do_pandas: reprova na main com PanicException (polars 2.0.0 e 1.0.0) e passa no branch.
  • test_interrupcao_na_conversao_da_saida_polars_sobe_em_vez_de_virar_object: o KeyboardInterrupt levantado na conversão de uma coluna sobe. Passa na main e no branch; guarda contra capturar BaseException.

Suíte inteira, mesmo ambiente para main e branch (pytest, sem o extra codex, como o job unit):

Ambiente main Branch
Python 3.13, polars 2.0.0 (dev all search-all) 1220 passed, 105 skipped 1222 passed, 105 skipped
Python 3.11, polars 2.0.0 (dev all search-all) 1220 passed, 105 skipped 1222 passed, 105 skipped
Python 3.11, lowest-direct com all excel polars search-all (polars 1.0.0, pyarrow 10.0.1, pandas 2.1.2) 1219 passed, 106 skipped 1221 passed, 106 skipped
Python 3.11, polars 1.9.0 não rodado 1222 passed, 105 skipped
Testes do branch sobre o código da main, Python 3.13 e 3.11 1 failed (decimal_acima_de_38_digitos), 1221 passed, 105 skipped

Os 105 pulados são do provider codex sem o SDK instalado e de parâmetros vazios; no lowest-direct, o 106º é langchain-core sem ModelError. São os mesmos na main e no branch.

Cobertura de ramos de src/dataframeit/utils.py, somando 3.13 e 3.11: 338 linhas, 170 ramos, 100%.

Qualidade, com os extras do job quality: ruff format --check src tests, ruff check src tests, ty check, vulture e deptry src passam.

Mutações, cada uma numa cópia de src com o import conferido:

Mutação Resultado
except Exception: (desfaz a correção) reprova decimal_acima_de_38_digitos
except BaseException: reprova test_interrupcao_na_conversao_da_saida_polars_sobe_em_vez_de_virar_object

SHA revisado: 7476876

@bdcdo

bdcdo commented Oct 14, 2026

Copy link
Copy Markdown
Owner Author

Revisão independente de 74768764: sem achado que bloqueie

Um subagente novo reproduziu a prova no polars 1.0.0 (piso, com pandas 2.1.2 e pyarrow 10.0.1) e no 2.0.0: a main sai com PanicException e o branch devolve a coluna Object com os três valores e o aviso. Fora da tabela, em execução completa, interrompida e retomada, o branch converte em Object também Decimal com escala 50, negativo de 60 dígitos, Optional[Decimal] com None primeiro, list[Decimal] com o valor grande e Any com dict que contém Decimal grande; no polars 1.6 a 1.9, Any com {} e None. O map_elements da coluna Object não entra em pânico em nenhuma das 17 versões testadas. As quatro mutações do diff morrem, inclusive a que captura BaseException, derrubada pelo teste do KeyboardInterrupt. Suítes: 1222 testes em Python 3.13 e 3.11, 1221 nas versões mínimas, 1175 no job do codex, cobertura de ramos combinada em 100%.

Dois casos ficam fora, os dois iguais na main:

  • No polars 1.6.0 a 1.9.0, uma coluna só com dicts vazios (Any com [{}, {}, {}]) volta da conversão com 0 linhas, sem levantar, e a montagem final cai com ShapeError. É o mesmo caso que o Campo do modelo que o Arrow não tipa sai como Object na saída polars #220 deixou de fora, porque pegá-lo pede uma conferência de tamanho que só essa faixa de versões exercitaria.
  • Decimal("NaN") com allow_inf_nan=True vira nulo sem aviso na saída polars.

@bdcdo
bdcdo merged commit 40d4f13 into main Oct 14, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Saída polars: pânico do polars na conversão final derruba a saída depois das chamadas (Decimal acima de 38 dígitos)

1 participant