Fix datetime comparison errors by normalizing to UTC (#988)

* Fix datetime comparison errors by normalizing to UTC

Applied ensure_utc() to all datetime comparisons in edge_operations.py to prevent TypeError when comparing timezone-naive and timezone-aware datetimes. Removed redundant tzinfo checks since ensure_utc() handles both None and naive datetimes.

Fixed comparisons at:
- Lines 419, 423: resolve_edge_contradictions function
- Line 430: resolve_edge_contradictions function
- Line 627: resolve_extracted_edge function (removed redundant tzinfo checks)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update uv.lock

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix sorting with mixed timezone-aware/naive datetimes

Normalize datetime to UTC in sort key to prevent TypeError when comparing mixed timezone-aware and timezone-naive datetimes during sorting.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Daniel Chalef 2025-10-07 08:28:56 -07:00 committed by GitHub
parent 27d4f1097b
commit 73015e980e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 21 additions and 16 deletions

View file

@ -413,21 +413,26 @@ def resolve_edge_contradictions(
invalidated_edges: list[EntityEdge] = [] invalidated_edges: list[EntityEdge] = []
for edge in invalidation_candidates: for edge in invalidation_candidates:
# (Edge invalid before new edge becomes valid) or (new edge invalid before edge becomes valid) # (Edge invalid before new edge becomes valid) or (new edge invalid before edge becomes valid)
edge_invalid_at_utc = ensure_utc(edge.invalid_at)
resolved_edge_valid_at_utc = ensure_utc(resolved_edge.valid_at)
edge_valid_at_utc = ensure_utc(edge.valid_at)
resolved_edge_invalid_at_utc = ensure_utc(resolved_edge.invalid_at)
if ( if (
edge.invalid_at is not None edge_invalid_at_utc is not None
and resolved_edge.valid_at is not None and resolved_edge_valid_at_utc is not None
and edge.invalid_at <= resolved_edge.valid_at and edge_invalid_at_utc <= resolved_edge_valid_at_utc
) or ( ) or (
edge.valid_at is not None edge_valid_at_utc is not None
and resolved_edge.invalid_at is not None and resolved_edge_invalid_at_utc is not None
and resolved_edge.invalid_at <= edge.valid_at and resolved_edge_invalid_at_utc <= edge_valid_at_utc
): ):
continue continue
# New edge invalidates edge # New edge invalidates edge
elif ( elif (
edge.valid_at is not None edge_valid_at_utc is not None
and resolved_edge.valid_at is not None and resolved_edge_valid_at_utc is not None
and edge.valid_at < resolved_edge.valid_at and edge_valid_at_utc < resolved_edge_valid_at_utc
): ):
edge.invalid_at = resolved_edge.valid_at edge.invalid_at = resolved_edge.valid_at
edge.expired_at = edge.expired_at if edge.expired_at is not None else utc_now() edge.expired_at = edge.expired_at if edge.expired_at is not None else utc_now()
@ -619,14 +624,14 @@ async def resolve_extracted_edge(
# Determine if the new_edge needs to be expired # Determine if the new_edge needs to be expired
if resolved_edge.expired_at is None: if resolved_edge.expired_at is None:
invalidation_candidates.sort(key=lambda c: (c.valid_at is None, c.valid_at)) invalidation_candidates.sort(key=lambda c: (c.valid_at is None, ensure_utc(c.valid_at)))
for candidate in invalidation_candidates: for candidate in invalidation_candidates:
candidate_valid_at_utc = ensure_utc(candidate.valid_at)
resolved_edge_valid_at_utc = ensure_utc(resolved_edge.valid_at)
if ( if (
candidate.valid_at candidate_valid_at_utc is not None
and resolved_edge.valid_at and resolved_edge_valid_at_utc is not None
and candidate.valid_at.tzinfo and candidate_valid_at_utc > resolved_edge_valid_at_utc
and resolved_edge.valid_at.tzinfo
and candidate.valid_at > resolved_edge.valid_at
): ):
# Expire new edge since we have information about more recent events # Expire new edge since we have information about more recent events
resolved_edge.invalid_at = candidate.valid_at resolved_edge.invalid_at = candidate.valid_at

2
uv.lock generated
View file

@ -783,7 +783,7 @@ wheels = [
[[package]] [[package]]
name = "graphiti-core" name = "graphiti-core"
version = "0.22.0rc4" version = "0.22.0rc5"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "diskcache" }, { name = "diskcache" },