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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR

### Corrigido

- Com entrada polars, um campo `Decimal` com valor acima de 38 dígitos deixa de derrubar a saída depois de todas as chamadas com o `PanicException` do polars, que não é `Exception`. A coluna sai `Object`, com aviso, como as demais que o polars recusa (#230).
- Com entrada polars, a saída deixa de falhar depois de todas as chamadas quando um campo do modelo guarda valores que o polars não reúne numa coluna tipada, como `Union[int, str]` com número numa linha e texto em outra, `tuple[int, str]`, `Any` com tipos diferentes, lista de `Enum`, inteiro acima de 2**53 ao lado de float, inteiro acima do `Int64` e `datetime` com fuso de deslocamento, como `+05:30`. A coluna sai com o tipo `Object`, com os objetos Python que a entrada pandas devolveria, e um aviso a nomeia. O campo `Enum` sai pelo valor do membro, inclusive na coluna `Object` (#218).
- A coluna `_trace` do agente único fica com as demais colunas de trace na ordem da saída, depois dos campos do modelo. Antes, numa entrada que já a trazia, os campos novos saíam depois dela.
- Com entrada polars, as colunas que a execução não escreve saem do DataFrame de entrada, com o tipo e os valores de lá. Antes, a saída inteira era convertida de volta do pandas: um `Int64` com nulo voltava `Float64`, o que arredondava ids acima de 2**53, um `Date` voltava `Datetime`, e `Enum`, `Decimal`, `Array`, `Null` e o inteiro dentro de `Struct` e `List` também trocavam de tipo; uma coluna `Object` também trocava de tipo ou, com objetos que o Arrow não infere, levantava `ArrowInvalid` depois das chamadas (#195).
Expand Down
7 changes: 5 additions & 2 deletions src/dataframeit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,13 @@ def from_pandas(
# As chamadas ao provedor já foram feitas, e a coluna que o polars ou o
# pyarrow recusam, seja qual for a exceção (ValueError, TypeError,
# OverflowError, ComputeError), sai Object em vez de derrubar a saída.
# O pânico do polars não é Exception e passa por aqui.
# O pânico do polars, como o do Decimal acima de 38 dígitos, que o
# pyarrow entrega em 256 bits, herda de BaseException e é capturado
# pelo nome; capturar BaseException engoliria o KeyboardInterrupt. O
# polars ainda escreve a mensagem do pânico no stderr.
try:
columns.append(_to_polars(values, model_columns.get(col)))
except Exception: # noqa: BLE001 (a recusa muda de tipo com os valores)
except (Exception, pl.exceptions.PanicException): # noqa: BLE001 (a recusa muda de tipo com os valores)
# Há versões do polars em que o construtor de Series inspeciona os
# valores mesmo com dtype=pl.Object: quando o primeiro valor não nulo
# é membro de Enum, troca cada valor pelo `.value`, e levanta erro no
Expand Down
14 changes: 14 additions & 0 deletions tests/test_entrada_e_saida.py
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,9 @@ def test_retomada_polars_de_campo_bool_gravado_como_inteiro(modelo):
datetime(2024, 1, 3, tzinfo=_FUSO_DE_DESLOCAMENTO),
],
),
# O pyarrow põe o Decimal acima de 38 dígitos numa coluna de 256 bits, que o
# polars não lê: a conversão entra em pânico, e o pânico não é Exception.
"decimal_acima_de_38_digitos": (Decimal, [Decimal("1E+40"), Decimal(2)]),
}


Expand Down Expand Up @@ -1085,6 +1088,17 @@ def test_campo_que_o_arrow_nao_tipa_com_entrada_polars_series():
assert resultado["campo"].to_list() == [1, "dois"]


def test_interrupcao_na_conversao_da_saida_polars_sobe_em_vez_de_virar_object():
"""A saída captura a recusa da conversão e o pânico do polars, e só eles."""
pl = pytest.importorskip("polars")

with (
patch("dataframeit.utils._to_polars", side_effect=KeyboardInterrupt),
pytest.raises(KeyboardInterrupt),
):
_rodar(pl.DataFrame({"texto": ["a", "b"]}))


def test_saida_polars_com_coluna_object_e_retomada():
pl = pytest.importorskip("polars")
modelo = create_model("ModeloDoCaso", campo=(Union[int, str], ...))
Expand Down
Loading