fix(timezone): isolate cache by active profile
This commit is contained in:
+21
-6
@@ -27,11 +27,22 @@ except ImportError:
|
||||
# Python 3.8 fallback (shouldn't be needed — Hermes requires 3.9+)
|
||||
from backports.zoneinfo import ZoneInfo # type: ignore[no-redef]
|
||||
|
||||
# Cached state — resolved once, reused on every call.
|
||||
# Cached state, keyed to the active timezone source. This process can multiplex
|
||||
# profiles by switching HERMES_HOME, so a single unkeyed value would leak the
|
||||
# first profile's timezone into later profile-scoped work.
|
||||
# Call reset_cache() to force re-resolution (e.g. after config changes).
|
||||
_cached_tz: Optional[ZoneInfo] = None
|
||||
_cached_tz_name: Optional[str] = None
|
||||
_cache_resolved: bool = False
|
||||
_cache_identity: Optional[tuple[str, str]] = None
|
||||
|
||||
|
||||
def _timezone_cache_identity() -> tuple[str, str]:
|
||||
"""Return the active source identity for the timezone cache."""
|
||||
tz_env = os.getenv("HERMES_TIMEZONE", "").strip()
|
||||
if tz_env:
|
||||
return ("environment", tz_env)
|
||||
return ("config", str(get_config_path()))
|
||||
|
||||
|
||||
def _resolve_timezone_name() -> str:
|
||||
@@ -94,14 +105,17 @@ def _get_zoneinfo(name: str) -> Optional[ZoneInfo]:
|
||||
|
||||
|
||||
def get_timezone() -> Optional[ZoneInfo]:
|
||||
"""Return the user's configured ZoneInfo, or None (meaning server-local).
|
||||
"""Return the active profile's configured ZoneInfo, or None (server-local).
|
||||
|
||||
Resolved once and cached. Call ``reset_cache()`` after config changes.
|
||||
The cache is isolated by the active environment override or profile config
|
||||
path. Call ``reset_cache()`` after editing the active config in place.
|
||||
"""
|
||||
global _cached_tz, _cached_tz_name, _cache_resolved
|
||||
if not _cache_resolved:
|
||||
global _cached_tz, _cached_tz_name, _cache_resolved, _cache_identity
|
||||
cache_identity = _timezone_cache_identity()
|
||||
if not _cache_resolved or _cache_identity != cache_identity:
|
||||
_cached_tz_name = _resolve_timezone_name()
|
||||
_cached_tz = _get_zoneinfo(_cached_tz_name)
|
||||
_cache_identity = cache_identity
|
||||
_cache_resolved = True
|
||||
return _cached_tz
|
||||
|
||||
@@ -113,10 +127,11 @@ def reset_cache() -> None:
|
||||
config edit or ``HERMES_TIMEZONE`` update) to force ``get_timezone()`` /
|
||||
``now()`` to read the new value instead of the value cached at first use.
|
||||
"""
|
||||
global _cached_tz, _cached_tz_name, _cache_resolved
|
||||
global _cached_tz, _cached_tz_name, _cache_resolved, _cache_identity
|
||||
_cached_tz = None
|
||||
_cached_tz_name = None
|
||||
_cache_resolved = False
|
||||
_cache_identity = None
|
||||
|
||||
|
||||
def now() -> datetime:
|
||||
|
||||
@@ -25,6 +25,7 @@ def _reset_hermes_time_cache():
|
||||
hermes_time._cached_tz = None
|
||||
hermes_time._cached_tz_name = None
|
||||
hermes_time._cache_resolved = False
|
||||
hermes_time._cache_identity = None
|
||||
|
||||
|
||||
# =========================================================================
|
||||
@@ -86,11 +87,28 @@ class TestGetTimezone:
|
||||
assert isinstance(tz, ZoneInfo)
|
||||
assert str(tz) == "Europe/London"
|
||||
|
||||
def test_cache_isolated_by_active_profile_config(self, tmp_path, monkeypatch):
|
||||
"""Switching HERMES_HOME must not reuse another profile's timezone."""
|
||||
first_home = tmp_path / "first"
|
||||
second_home = tmp_path / "second"
|
||||
first_home.mkdir()
|
||||
second_home.mkdir()
|
||||
(first_home / "config.yaml").write_text("timezone: Asia/Tokyo\n", encoding="utf-8")
|
||||
(second_home / "config.yaml").write_text(
|
||||
"timezone: America/New_York\n", encoding="utf-8"
|
||||
)
|
||||
monkeypatch.delenv("HERMES_TIMEZONE", raising=False)
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(first_home))
|
||||
assert str(hermes_time.get_timezone()) == "Asia/Tokyo"
|
||||
|
||||
# Multiplexed profile runtime scopes switch HERMES_HOME in one process.
|
||||
monkeypatch.setenv("HERMES_HOME", str(second_home))
|
||||
assert str(hermes_time.get_timezone()) == "America/New_York"
|
||||
|
||||
|
||||
# =========================================================================
|
||||
|
||||
# execute_code child env — TZ injection
|
||||
# =========================================================================
|
||||
|
||||
|
||||
Reference in New Issue
Block a user