generated from coulomb/repo-seed
Implement phase-memory foundation
This commit is contained in:
58
src/phase_memory/utils.py
Normal file
58
src/phase_memory/utils.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Small deterministic helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import is_dataclass, asdict
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
import hashlib
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
|
||||
def utc_now_iso() -> str:
|
||||
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
||||
|
||||
|
||||
def stable_digest(value: Any, *, length: int = 12) -> str:
|
||||
encoded = json.dumps(to_plain(value), sort_keys=True, separators=(",", ":")).encode()
|
||||
return hashlib.sha256(encoded).hexdigest()[:length]
|
||||
|
||||
|
||||
def compact_dict(mapping: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
key: to_plain(value)
|
||||
for key, value in mapping.items()
|
||||
if value not in (None, "", [], {}, ())
|
||||
}
|
||||
|
||||
|
||||
def to_plain(value: Any) -> Any:
|
||||
if isinstance(value, Enum):
|
||||
return value.value
|
||||
if is_dataclass(value):
|
||||
return to_plain(asdict(value))
|
||||
if isinstance(value, dict):
|
||||
return {
|
||||
str(key): to_plain(item)
|
||||
for key, item in value.items()
|
||||
if item not in (None, "", [], {}, ())
|
||||
}
|
||||
if isinstance(value, (list, tuple, set)):
|
||||
return [to_plain(item) for item in value]
|
||||
return value
|
||||
|
||||
|
||||
def parse_iso_datetime(value: str | None) -> datetime | None:
|
||||
if not value:
|
||||
return None
|
||||
raw = value.strip()
|
||||
if raw.endswith("Z"):
|
||||
raw = raw[:-1] + "+00:00"
|
||||
try:
|
||||
parsed = datetime.fromisoformat(raw)
|
||||
except ValueError:
|
||||
return None
|
||||
if parsed.tzinfo is None:
|
||||
return parsed.replace(tzinfo=timezone.utc)
|
||||
return parsed.astimezone(timezone.utc)
|
||||
Reference in New Issue
Block a user