From 0adc39ab7a86f78a23038958ce322470cef1d4da Mon Sep 17 00:00:00 2001 From: chopratejas Date: Thu, 19 Feb 2026 11:03:24 -0800 Subject: [PATCH] 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 --- headroom/cli/memory.py | 8 ++++---- headroom/client.py | 4 ++-- headroom/memory/adapters/sqlite.py | 4 ++-- headroom/memory/backends/mem0.py | 14 +++++++++++--- headroom/utils.py | 4 ++-- tests/test_compress_api.py | 8 ++------ tests/test_compression/test_evals.py | 10 +++++----- tests/test_compression/test_llm_eval.py | 8 ++++---- tests/test_memory/test_hierarchical.py | 10 +++++++--- tests/test_memory_system.py | 4 ++-- 10 files changed, 41 insertions(+), 33 deletions(-) diff --git a/headroom/cli/memory.py b/headroom/cli/memory.py index e176d61ae..1c033033d 100644 --- a/headroom/cli/memory.py +++ b/headroom/cli/memory.py @@ -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: diff --git a/headroom/client.py b/headroom/client.py index 1aa746c54..dabed3e25 100644 --- a/headroom/client.py +++ b/headroom/client.py @@ -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) diff --git a/headroom/memory/adapters/sqlite.py b/headroom/memory/adapters/sqlite.py index a4cc3185f..f7ab0a939 100644 --- a/headroom/memory/adapters/sqlite.py +++ b/headroom/memory/adapters/sqlite.py @@ -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) diff --git a/headroom/memory/backends/mem0.py b/headroom/memory/backends/mem0.py index 92dfa8478..c196155e8 100644 --- a/headroom/memory/backends/mem0.py +++ b/headroom/memory/backends/mem0.py @@ -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 diff --git a/headroom/utils.py b/headroom/utils.py index 512715721..6f6d9195b 100644 --- a/headroom/utils.py +++ b/headroom/utils.py @@ -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" diff --git a/tests/test_compress_api.py b/tests/test_compress_api.py index 9b3795076..1c0d15659 100644 --- a/tests/test_compress_api.py +++ b/tests/test_compress_api.py @@ -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 diff --git a/tests/test_compression/test_evals.py b/tests/test_compression/test_evals.py index 01e3486da..f3822ebbe 100644 --- a/tests/test_compression/test_evals.py +++ b/tests/test_compression/test_evals.py @@ -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 diff --git a/tests/test_compression/test_llm_eval.py b/tests/test_compression/test_llm_eval.py index 5f35de03d..6570e8e46 100644 --- a/tests/test_compression/test_llm_eval.py +++ b/tests/test_compression/test_llm_eval.py @@ -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: diff --git a/tests/test_memory/test_hierarchical.py b/tests/test_memory/test_hierarchical.py index afe86fbd0..d5e016921 100644 --- a/tests/test_memory/test_hierarchical.py +++ b/tests/test_memory/test_hierarchical.py @@ -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) diff --git a/tests/test_memory_system.py b/tests/test_memory_system.py index f5eccd2ea..2409d30a9 100644 --- a/tests/test_memory_system.py +++ b/tests/test_memory_system.py @@ -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(