Skip to content

Saída polars: campo que aceita date e datetime perde a hora quando a primeira resposta é só data #228

Closed

Description

@bdcdo

Com entrada polars, um campo que aceita data e data com hora perde a hora quando a primeira resposta é só data. A coluna sai Date, sem erro e sem aviso. Com entrada pandas, as duas respostas saem como vieram.

import datetime as dt
import warnings
from typing import Union
from unittest.mock import patch

import pandas as pd
import polars as pl
from pydantic import BaseModel

from dataframeit import dataframeit


def rodar(df, modelo, respostas, **opcoes):
    fila = iter(respostas)

    def llm(*args, **kwargs):
        resposta = next(fila)
        if isinstance(resposta, Exception):
            raise resposta
        return {"data": resposta, "usage": None}

    with patch("dataframeit.core.call_langchain", side_effect=llm), \
         patch("dataframeit.core.validate_provider_dependencies"), \
         warnings.catch_warnings():
        warnings.simplefilter("ignore")
        return dataframeit(df, questions=modelo, prompt="{texto}", track_tokens=False, max_retries=1, **opcoes)


class M(BaseModel):
    quando: Union[dt.datetime, dt.date]


respostas = [{"quando": dt.date(2026, 1, 2)}, {"quando": dt.datetime(2026, 1, 2, 15, 30)}]
print(rodar(pd.DataFrame({"texto": ["a", "b"]}), M, list(respostas))["quando"].tolist())
# [datetime.date(2026, 1, 2), datetime.datetime(2026, 1, 2, 15, 30)]

saida = rodar(pl.DataFrame({"texto": ["a", "b"]}), M, list(respostas))
print(saida.schema["quando"], saida["quando"].to_list())
# Date [datetime.date(2026, 1, 2), datetime.date(2026, 1, 2)]

A resposta 2026-01-02 15:30 sai 2026-01-02. Na ordem inversa, com a data e hora primeiro, a conversão recusa a coluna: na main levanta ArrowTypeError depois das chamadas (#218), e com o #220 a coluna sai Object com os dois valores e o aviso.

Causa

pl.from_pandas infere o tipo da coluna de objetos pelo primeiro valor. Com date primeiro, o pyarrow monta uma coluna de datas e aceita o datetime seguinte truncando a hora, porque datetime é subclasse de date.

Proposta

É mais um caso de tipo decidido pelos dados e não pela anotação, como os da #219. Quando a anotação do campo admite datetime, a coluna sai Datetime, com a data pura à meia-noite, ou Object com aviso, se a distinção entre os dois tipos tiver de ser preservada. O teste é o trecho acima nas duas ordens.

Activity

  1. bdcdo commented on Oct 13, 2026

    @bdcdo
    OwnerAuthor

    A mesma troca calada acontece em outras uniões com data ou hora, reproduzida na main depois do #220, com polars 2.0.0. O pyarrow escolhe o tipo pelo primeiro valor e converte o inteiro seguinte em vez de recusar a coluna:

    class Hora(BaseModel):
        campo: Union[dt.time, int]
    
    class Data(BaseModel):
        campo: Union[dt.date, int]
    
    # respostas: time(1, 2), 86400000000, time(3, 0)
    # Time [datetime.time(1, 2), None, datetime.time(3, 0)]
    
    # respostas: date(2026, 1, 2), 5, date(2026, 1, 3)
    # Date [datetime.date(2026, 1, 2), datetime.date(1970, 1, 6), datetime.date(2026, 1, 3)]

    No primeiro, o inteiro fora do intervalo de um dia vira nulo (do polars 1.8.2 em diante; até a 1.7.1 vira time(0, 0)). No segundo, o inteiro 5 vira o quinto dia depois de 1970-01-01, em todas as versões testadas de 1.0.0 a 2.0.0. Com entrada pandas, os três valores saem como vieram.

    A regra pela anotação resolve os três casos: quando a união do campo mistura tipos que o pyarrow converte entre si, a coluna não pode ser inferida pelos valores.

  2. bdcdo commented on Oct 14, 2026

    @bdcdo
    OwnerAuthor

    Mais dois casos da mesma família, agora dentro de estrutura, reproduzidos na main em c6e99a10 com polars 2.0.0. Os dois aparecem numa execução sem interrupção:

    tuple[date, int] com (date(2026, 1, 2), 5)
      List(Date) [[datetime.date(2026, 1, 2), datetime.date(1970, 1, 6)]]   sem aviso
    
    list[Union[date, datetime]] com [date(2026, 1, 2), datetime(2026, 1, 2, 10, 0)]
      List(Date) [[datetime.date(2026, 1, 2), datetime.date(2026, 1, 2)]]   sem aviso
    

    No primeiro, o inteiro 5 vira o quinto dia depois de 1970-01-01. No segundo, a hora some. A revisão do #233 viu os dois no polars 1.0.0, 1.20.0 e 2.0.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions