Fix CI: guard starlette imports, asyncio.run(), deprecate datetime.utcnow()

- Guard starlette imports in test_compress_api.py (skip ASGI tests without proxy deps)
- Replace asyncio.get_event_loop().run_until_complete() with asyncio.run() (Python 3.13)
- Replace datetime.utcnow() with datetime.now(timezone.utc).replace(tzinfo=None) everywhere
This commit is contained in:
chopratejas 2026-02-19 11:03:24 -08:00
parent 0a434531d8
commit 0adc39ab7a
10 changed files with 41 additions and 33 deletions

View file

@ -6,7 +6,7 @@ import asyncio
import json
import os
import sys
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any
@ -102,7 +102,7 @@ def _get_stats(store: SQLiteMemoryStore) -> dict[str, Any]:
stats["by_scope"] = by_scope
# Count by age buckets
now = datetime.utcnow()
now = datetime.now(timezone.utc).replace(tzinfo=None)
by_age: dict[str, int] = {}
one_day_ago = (now - timedelta(days=1)).isoformat()
@ -302,7 +302,7 @@ def list_memories(
if since_duration:
duration = parse_duration(since_duration)
cutoff = datetime.utcnow() - duration
cutoff = datetime.now(timezone.utc).replace(tzinfo=None) - duration
filter_kwargs["created_after"] = cutoff
mem_filter = MemoryFilter(**filter_kwargs)
@ -692,7 +692,7 @@ def prune_memories(
if older_than:
duration = parse_duration(older_than)
cutoff = datetime.utcnow() - duration
cutoff = datetime.now(timezone.utc).replace(tzinfo=None) - duration
filter_kwargs["created_before"] = cutoff
if scope:

View file

@ -3,7 +3,7 @@
from __future__ import annotations
from collections.abc import Iterator
from datetime import datetime
from datetime import datetime, timezone
from typing import Any
from .cache import (
@ -388,7 +388,7 @@ class HeadroomClient:
) -> Any:
"""Internal implementation of create."""
request_id = generate_request_id()
timestamp = datetime.utcnow()
timestamp = datetime.now(timezone.utc).replace(tzinfo=None)
mode = HeadroomMode(headroom_mode) if headroom_mode else self._default_mode
tokenizer = self._get_tokenizer(model)

View file

@ -12,7 +12,7 @@ from __future__ import annotations
import json
import re
import sqlite3
from datetime import datetime
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
@ -606,7 +606,7 @@ class SQLiteMemoryStore:
ValueError: If the old memory is not found.
"""
if supersede_time is None:
supersede_time = datetime.utcnow()
supersede_time = datetime.now(timezone.utc).replace(tzinfo=None)
# Get the old memory
old_memory = await self.get(old_memory_id)

View file

@ -14,7 +14,7 @@ from __future__ import annotations
import asyncio
import uuid
from dataclasses import dataclass
from datetime import datetime
from datetime import datetime, timezone
from typing import Any
from headroom.memory.models import Memory
@ -217,10 +217,18 @@ class Mem0Backend:
# Parse timestamps
created_at_str = metadata.get("created_at")
created_at = datetime.fromisoformat(created_at_str) if created_at_str else datetime.utcnow()
created_at = (
datetime.fromisoformat(created_at_str)
if created_at_str
else datetime.now(timezone.utc).replace(tzinfo=None)
)
valid_from_str = metadata.get("valid_from")
valid_from = datetime.fromisoformat(valid_from_str) if valid_from_str else datetime.utcnow()
valid_from = (
datetime.fromisoformat(valid_from_str)
if valid_from_str
else datetime.now(timezone.utc).replace(tzinfo=None)
)
valid_until_str = metadata.get("valid_until")
valid_until = datetime.fromisoformat(valid_until_str) if valid_until_str else None

View file

@ -6,7 +6,7 @@ import hashlib
import json
import re
import uuid
from datetime import datetime
from datetime import datetime, timezone
from typing import Any
# Marker format for Headroom modifications
@ -69,7 +69,7 @@ def compute_prefix_hash(messages: list[dict[str, Any]], prefix_count: int | None
def format_timestamp(dt: datetime | None = None) -> str:
"""Format datetime as ISO8601 string."""
if dt is None:
dt = datetime.utcnow()
dt = datetime.now(timezone.utc).replace(tzinfo=None)
return dt.isoformat() + "Z"

View file

@ -231,9 +231,7 @@ class TestLiteLLMCallback:
],
}
result = asyncio.get_event_loop().run_until_complete(
callback.async_pre_call_hook("key", data, "completion")
)
result = asyncio.run(callback.async_pre_call_hook("key", data, "completion"))
assert result is data
def test_callback_ignores_non_completion(self):
@ -245,7 +243,5 @@ class TestLiteLLMCallback:
callback = HeadroomCallback()
data = {"messages": [{"role": "user", "content": "hi"}]}
result = asyncio.get_event_loop().run_until_complete(
callback.async_pre_call_hook("key", data, "embedding")
)
result = asyncio.run(callback.async_pre_call_hook("key", data, "embedding"))
assert result is data

View file

@ -121,7 +121,7 @@ This module provides JWT-based authentication with role-based access control.
from __future__ import annotations
import logging
from datetime import datetime, timedelta
from datetime import datetime, timezone, timedelta
from typing import Optional, List, Any
import jwt
@ -181,16 +181,16 @@ def create_access_token(
>>> print(token) # eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
"""
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = datetime.now(timezone.utc).replace(tzinfo=None) + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=TOKEN_EXPIRE_MINUTES)
expire = datetime.now(timezone.utc).replace(tzinfo=None) + timedelta(minutes=TOKEN_EXPIRE_MINUTES)
payload = {
"sub": user_id,
"exp": expire,
"roles": roles or [],
"permissions": permissions or [],
"iat": datetime.utcnow(),
"iat": datetime.now(timezone.utc).replace(tzinfo=None),
}
encoded_jwt = jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
@ -313,7 +313,7 @@ class RateLimiter:
Returns:
True if request is allowed, False if rate limited.
"""
now = datetime.utcnow()
now = datetime.now(timezone.utc).replace(tzinfo=None)
cutoff = now - timedelta(seconds=self.window_size)
# Clean old entries

View file

@ -119,7 +119,7 @@ PRODUCT_CATALOG = json.dumps(
CODE_FILE = '''"""User authentication service with JWT tokens."""
from datetime import datetime, timedelta
from datetime import datetime, timezone, timedelta
from typing import Optional
import jwt
from pydantic import BaseModel
@ -155,9 +155,9 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -
"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
expire = datetime.now(timezone.utc).replace(tzinfo=None) + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
expire = datetime.now(timezone.utc).replace(tzinfo=None) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
@ -215,7 +215,7 @@ class RateLimiter:
def is_allowed(self, client_id: str) -> bool:
"""Check if a request from client_id is allowed."""
now = datetime.utcnow()
now = datetime.now(timezone.utc).replace(tzinfo=None)
cutoff = now - timedelta(seconds=self.window_seconds)
if client_id not in self._requests:

View file

@ -22,7 +22,7 @@ os.environ["TOKENIZERS_PARALLELISM"] = "false"
import asyncio
import tempfile
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from pathlib import Path
import numpy as np
@ -113,7 +113,11 @@ class TestMemoryModel:
current = Memory(content="test", user_id="alice")
assert current.is_current is True
superseded = Memory(content="test", user_id="alice", valid_until=datetime.utcnow())
superseded = Memory(
content="test",
user_id="alice",
valid_until=datetime.now(timezone.utc).replace(tzinfo=None),
)
assert superseded.is_current is False
def test_memory_serialization(self, sample_embedding):
@ -555,7 +559,7 @@ class TestIntegration:
# Supersede
new_memory = Memory(content="New preference", user_id="alice")
supersede_time = datetime.utcnow()
supersede_time = datetime.now(timezone.utc).replace(tzinfo=None)
await store.supersede(original.id, new_memory, supersede_time)
# Query at a point when original was valid (after its valid_from, before supersession)

View file

@ -17,7 +17,7 @@ import os
os.environ["TOKENIZERS_PARALLELISM"] = "false"
import tempfile
from datetime import datetime
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
@ -960,7 +960,7 @@ class MockBackend:
)
self._memories[new_memory.id] = new_memory
old_memory.superseded_by = new_memory.id
old_memory.valid_until = datetime.utcnow()
old_memory.valid_until = datetime.now(timezone.utc).replace(tzinfo=None)
return new_memory
async def delete_memory(