Description
I have the following document class, along with a helper method that adapts objects from our existing SQL database:
class EsIdCard(InnerDoc):
id: int
card_number: str
expiration_date: date
@staticmethod
def from_sql_id_card(sql_id_card: SqlIdCard) -> "EsIdCard":
return EsIdCard(
id=sql_id_card.id,
card_number=sql_id_card.card_number,
expiration_date=sql_id_card.expiration_date,
)
When I save IdCard
objects to the database, it behaves how I would expect:
print(expiration) # => datetime.date(2025, 10, 4)
new_id_card: SqlIdCard = SqlIdCard(expiration_date=expiration, card_number=num, owner_id=person.id)
# save to SQL, re-fetch object (to get auto-incremented ID), then:
es_person.id_card = EsIdCard.from_sql_id_card(new_id_card) # => EsIdCard(expiration_date=datetime.date(2025, 10, 4), ...)
es_person.save()
curl -X GET localhost:9200/people/_doc/123 -H "Content-Type: application/json" | python -m json.tool
{
"_index": "people",
"_id": "123",
"...": "...",
"_source": {
"...": "...",
"id_card": {
"id": 1234,
"card_number": "1234-5678",
"expiration_date": "2025-10-04"
}
}
}
However, when I go to retrieve the person via the DSL class, the dates get casted into datetime objects:
person: EsPerson = EsPerson.get(id=123, index="people")
print(person.id_card.expiration_date) # => datetime.datetime(2025, 10, 4, 0, 0)
As a result, unless I manually re-cast the date fields to date, modifying the person and calling .save()
throws an exception:
person.unrelated_attribute = "foo"
person.save() # => elasticsearch.BadRequestError: BadRequestError(400, 'document_parsing_exception', "[1:68] failed to parse field [id_card.expiration_date] of type [date] in document with id '123'. Preview of field's value: '2025-10-04T00:00:00'")
According to the type mapping documentation, this would seem to be an error. If anything, I would expect the reverse to occur (casting datetimes into dates) based on the type mapping table.
Is casting dates to datetimes the expected behavior? If so, should I be using a different type hint for dates that will never have times?
Edit: If I execute a search, the fields remain dates until they get converted into the DSL class:
results: Response = EsPerson.search(index="people").execute()
people: list[EsPerson] = list(results)
print(results.hits.hits[0]['_source']['id_card']['expiration_date']) # => '2025-10-04'
print(people[0].id_card.expiration_date) # => datetime.datetime(2025, 10, 4, 0, 0)