mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-01 19:18:35 -04:00
fix(skills): replace deprecated utcnow in skill timestamp helper (#5777)
* fix(skills): replace deprecated utcnow in skill timestamp helper _now_iso() builds the 'created' value in skill frontmatter. datetime.utcnow() returns a naive datetime and has been deprecated since Python 3.12, scheduled for removal. Switch to the timezone-aware datetime.now(timezone.utc), keeping the serialized YYYY-MM-DDTHH:MM:SSZ shape unchanged so existing skill files keep parsing. timezone.utc is used rather than the datetime.UTC alias, which is 3.11+ only. Adds regression tests covering the deprecation, the serialized shape, and UTC correctness under a non-UTC local timezone -- the last guards against a bare datetime.now(), which yields the same shape but local wall time. Fixes #5697 * test(skills): skip timezone mutation where unsupported --------- Co-authored-by: Alexandre Teixeira <alexandremagteixeira@gmail.com>
This commit is contained in:
@@ -50,7 +50,7 @@ import json
|
||||
import logging
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -441,4 +441,4 @@ class Skill:
|
||||
|
||||
|
||||
def _now_iso() -> str:
|
||||
return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
"""Regression for issue #5697 — skill timestamps must not use ``datetime.utcnow()``.
|
||||
|
||||
``_now_iso()`` builds the ``created`` value in skill frontmatter. ``utcnow()``
|
||||
returns a *naive* datetime and has been deprecated since Python 3.12, scheduled
|
||||
for removal. The replacement must stay timezone-aware while keeping the
|
||||
serialized ``YYYY-MM-DDTHH:MM:SSZ`` shape, so skill files written by older
|
||||
versions keep parsing.
|
||||
|
||||
The UTC check matters on its own: a bare ``datetime.now()`` also produces the
|
||||
right shape, but emits local wall time, which would silently backdate or
|
||||
postdate skills for every user outside UTC.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import warnings
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from services.memory.skill_format import _now_iso
|
||||
|
||||
_ISO_Z = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$")
|
||||
|
||||
|
||||
def test_now_iso_keeps_serialized_shape():
|
||||
assert _ISO_Z.match(_now_iso())
|
||||
|
||||
|
||||
def test_now_iso_emits_no_deprecation_warning():
|
||||
with warnings.catch_warnings(record=True) as caught:
|
||||
warnings.simplefilter("always")
|
||||
_now_iso()
|
||||
assert not [w for w in caught if issubclass(w.category, DeprecationWarning)]
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not hasattr(time, "tzset"),
|
||||
reason="time.tzset is unavailable on this platform",
|
||||
)
|
||||
def test_now_iso_is_utc_not_local_time():
|
||||
"""Pin UTC under a non-UTC local timezone, where the two visibly diverge."""
|
||||
original_tz = os.environ.get("TZ")
|
||||
os.environ["TZ"] = "Asia/Amman" # UTC+3, never UTC
|
||||
time.tzset()
|
||||
try:
|
||||
emitted = datetime.strptime(_now_iso(), "%Y-%m-%dT%H:%M:%SZ").replace(
|
||||
tzinfo=timezone.utc
|
||||
)
|
||||
drift = abs((emitted - datetime.now(timezone.utc)).total_seconds())
|
||||
assert drift < 60, f"timestamp is {drift}s off UTC — local time leaked in"
|
||||
finally:
|
||||
if original_tz is None:
|
||||
os.environ.pop("TZ", None)
|
||||
else:
|
||||
os.environ["TZ"] = original_tz
|
||||
time.tzset()
|
||||
Reference in New Issue
Block a user