mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-15 21:16:03 +08:00
Fix DateTimeTzField crashing on driver-native datetimes and unparseable values (#18264)
This commit is contained in:
@@ -1316,15 +1316,24 @@ class DateTimeTzField(CharField):
|
||||
return value.replace(tzinfo=timezone.utc).isoformat()
|
||||
return value
|
||||
|
||||
def python_value(self, value: str | None) -> datetime | None:
|
||||
if value is not None:
|
||||
def python_value(self, value: str | datetime | None) -> datetime | None:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, datetime):
|
||||
# The column is declared VARCHAR, but deployments upgraded from
|
||||
# older schemas may hold it as native DATETIME, in which case the
|
||||
# driver returns datetime objects instead of ISO strings.
|
||||
return value if value.tzinfo is not None else value.replace(tzinfo=timezone.utc)
|
||||
try:
|
||||
dt = datetime.fromisoformat(value)
|
||||
if dt.tzinfo is None:
|
||||
import pytz
|
||||
|
||||
return dt.replace(tzinfo=pytz.UTC)
|
||||
return dt
|
||||
return value
|
||||
except ValueError:
|
||||
# Zero dates (0000-00-00) and other unparseable values must not
|
||||
# raise here: peewee counts the row before converting it, so one
|
||||
# bad value wedges every later iteration over the table with
|
||||
# IndexError: list index out of range.
|
||||
logging.warning("DateTimeTzField: unparseable value %r, falling back to None", value)
|
||||
return None
|
||||
return dt if dt.tzinfo is not None else dt.replace(tzinfo=timezone.utc)
|
||||
|
||||
|
||||
class SyncLogs(DataBaseModel):
|
||||
|
||||
62
test/unit_test/api/db/test_datetime_tz_field.py
Normal file
62
test/unit_test/api/db/test_datetime_tz_field.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Tests for DateTimeTzField conversion between database driver values and
|
||||
Python datetimes.
|
||||
|
||||
The sync_logs poll_range columns are declared VARCHAR, but deployments
|
||||
upgraded from older schemas can hold them in native DATETIME columns, so the
|
||||
driver may hand back datetime objects (or zero-date strings) instead of ISO
|
||||
strings. Conversion must never raise: one bad value previously wedged the
|
||||
whole sync-task scheduler with "list index out of range".
|
||||
"""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from api.db.db_models import DateTimeTzField
|
||||
|
||||
|
||||
class TestDateTimeTzFieldPythonValue:
|
||||
def test_none(self):
|
||||
assert DateTimeTzField().python_value(None) is None
|
||||
|
||||
def test_iso_string_with_timezone(self):
|
||||
dt = DateTimeTzField().python_value("2026-08-14T10:00:00.123456+00:00")
|
||||
assert dt == datetime(2026, 8, 14, 10, 0, 0, 123456, tzinfo=UTC)
|
||||
|
||||
def test_iso_string_without_timezone_assumes_utc(self):
|
||||
dt = DateTimeTzField().python_value("2026-08-14 10:00:00")
|
||||
assert dt == datetime(2026, 8, 14, 10, 0, tzinfo=UTC)
|
||||
|
||||
def test_native_datetime_returned_by_driver(self):
|
||||
naive = datetime(2026, 8, 14, 10, 0, 0, 123000, tzinfo=UTC).replace(tzinfo=None)
|
||||
dt = DateTimeTzField().python_value(naive)
|
||||
assert dt == datetime(2026, 8, 14, 10, 0, 0, 123000, tzinfo=UTC)
|
||||
|
||||
def test_native_aware_datetime_kept(self):
|
||||
aware = datetime(2026, 8, 14, 10, 0, tzinfo=UTC)
|
||||
assert DateTimeTzField().python_value(aware) is aware
|
||||
|
||||
def test_zero_date_string_falls_back_to_none(self):
|
||||
assert DateTimeTzField().python_value("0000-00-00 00:00:00") is None
|
||||
|
||||
def test_garbage_string_falls_back_to_none(self):
|
||||
assert DateTimeTzField().python_value("not-a-date") is None
|
||||
|
||||
def test_empty_string_falls_back_to_none(self):
|
||||
assert DateTimeTzField().python_value("") is None
|
||||
|
||||
|
||||
class TestDateTimeTzFieldDbValue:
|
||||
def test_none(self):
|
||||
assert DateTimeTzField().db_value(None) is None
|
||||
|
||||
def test_naive_datetime_written_as_utc_iso(self):
|
||||
naive = datetime(2026, 8, 14, 10, 0, tzinfo=UTC).replace(tzinfo=None)
|
||||
assert DateTimeTzField().db_value(naive) == "2026-08-14T10:00:00+00:00"
|
||||
|
||||
def test_aware_datetime_written_as_iso(self):
|
||||
assert DateTimeTzField().db_value(datetime(2026, 8, 14, 10, 0, tzinfo=UTC)) == "2026-08-14T10:00:00+00:00"
|
||||
|
||||
def test_db_value_round_trips_through_python_value(self):
|
||||
field = DateTimeTzField()
|
||||
stored = field.db_value(datetime(2026, 8, 14, 10, 0, 0, 123456, tzinfo=UTC))
|
||||
assert field.python_value(stored) == datetime(2026, 8, 14, 10, 0, 0, 123456, tzinfo=UTC)
|
||||
Reference in New Issue
Block a user