From 6987d13d43c199ec1b60c8fefca72c82d074ddbd Mon Sep 17 00:00:00 2001 From: devteamaegis Date: Mon, 1 Jun 2026 15:07:10 -0400 Subject: [PATCH 1/2] fix(util): guard _datetime_from_weaviate_str against empty string input Empty string is the protobuf wire-format default for an unset string field. Calling string[-1] on "" raises IndexError, crashing gRPC deserialization for any object with an unset date property. Return datetime.min for empty input, matching the existing behaviour for year-zero dates. Fixes #2052 --- test/test_util.py | 2 ++ weaviate/util.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/test/test_util.py b/test/test_util.py index 417cb77ef..1d3f2bdea 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -458,6 +458,8 @@ def test_is_weaviate_too_old(version: str, too_old: bool): ), # Test handling year 0 (should return datetime.min) ("0000-01-15T14:30:45.123456Z", datetime.min), + # Test empty string (protobuf default for unset string field) + ("", datetime.min), ], ) def test_datetime_from_weaviate_str(input_str: str, expected: datetime) -> None: diff --git a/weaviate/util.py b/weaviate/util.py index 7ee9e5566..67de55bb2 100644 --- a/weaviate/util.py +++ b/weaviate/util.py @@ -751,6 +751,8 @@ def _datetime_to_string(value: TIME) -> str: def _datetime_from_weaviate_str(string: str) -> datetime.datetime: + if not string: + return datetime.datetime.min if string[-1] != "Z": string = "".join(string.rsplit(":", 1)) From d6b8db953e04d2e7cd93f0135edd75fce959f1d4 Mon Sep 17 00:00:00 2001 From: devteamaegis Date: Sun, 26 Jul 2026 12:09:59 -0400 Subject: [PATCH 2/2] Return None for unset datetime instead of datetime.min Per review (@dirkkul): an unset date property should surface as None, not a datetime.min sentinel. Empty-string input now returns None; year-0 out-of-range handling is unchanged. Callers propagate the value as a property (typed Any / Optional[List[Any]]) so None flows through. --- test/test_util.py | 9 ++++++--- weaviate/util.py | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test/test_util.py b/test/test_util.py index 1d3f2bdea..d893d4281 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -2,6 +2,7 @@ import uuid as uuid_lib from copy import deepcopy from datetime import datetime, timedelta, timezone +from typing import Optional from unittest.mock import Mock, patch import pytest @@ -458,11 +459,13 @@ def test_is_weaviate_too_old(version: str, too_old: bool): ), # Test handling year 0 (should return datetime.min) ("0000-01-15T14:30:45.123456Z", datetime.min), - # Test empty string (protobuf default for unset string field) - ("", datetime.min), + # Test empty string (protobuf default for unset string field) -> None + ("", None), ], ) -def test_datetime_from_weaviate_str(input_str: str, expected: datetime) -> None: +def test_datetime_from_weaviate_str( + input_str: str, expected: Optional[datetime] +) -> None: assert _datetime_from_weaviate_str(input_str) == expected diff --git a/weaviate/util.py b/weaviate/util.py index 67de55bb2..925643690 100644 --- a/weaviate/util.py +++ b/weaviate/util.py @@ -750,9 +750,9 @@ def _datetime_to_string(value: TIME) -> str: return value.isoformat(sep="T", timespec="microseconds") -def _datetime_from_weaviate_str(string: str) -> datetime.datetime: +def _datetime_from_weaviate_str(string: str) -> Optional[datetime.datetime]: if not string: - return datetime.datetime.min + return None if string[-1] != "Z": string = "".join(string.rsplit(":", 1))