Coverage for src/objectstate/snapshot_model.py: 98%
43 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 07:22 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 07:22 +0000
1"""
2Snapshot and Timeline dataclasses for git-like time-travel history.
4This module provides typed data structures for the ObjectStateRegistry's
5time-travel system, replacing the tuple-based implementation.
7Design Philosophy: Correct by Construction
8- No Optional fields for required data
9- Immutable snapshots (frozen dataclass)
10- UUID-based identity for snapshots
11- Direct attribute access (no getattr fallbacks)
12"""
14from dataclasses import asdict, dataclass, field, fields
15from typing import Dict, Optional, List
16from objectstate.construction_binding import StateConstructionBinding
17import uuid
18import time
21@dataclass(frozen=True)
22class StateSnapshot:
23 """Immutable snapshot of a single ObjectState's data.
25 Captures the resolved values, parameters, and provenance at a point in time.
26 Construction declarations are typed values, never live lifecycle resources.
27 """
29 saved_resolved: Dict
30 live_resolved: Dict
31 parameters: Dict # Current concrete values
32 saved_parameters: Dict # Concrete values at last save (for concrete dirty detection)
33 provenance: Dict
34 construction: StateConstructionBinding
35 meta: Dict = field(default_factory=dict)
38@dataclass(frozen=True)
39class Snapshot:
40 """Immutable snapshot of ALL ObjectStates at a point in time.
42 Analogous to a git commit - captures the entire system state.
43 """
45 id: str # UUID string
46 timestamp: float
47 label: str
48 triggering_scope: Optional[str] # scope_id that triggered the snapshot
49 parent_id: Optional[str] # UUID of parent snapshot (None for root)
50 all_states: Dict[str, StateSnapshot] # scope_id → StateSnapshot
52 @classmethod
53 def create(
54 cls,
55 label: str,
56 all_states: Dict[str, StateSnapshot],
57 triggering_scope: Optional[str] = None,
58 parent_id: Optional[str] = None,
59 ) -> "Snapshot":
60 """Create a new snapshot with auto-generated ID and timestamp."""
61 return cls(
62 id=str(uuid.uuid4()),
63 timestamp=time.time(),
64 label=label,
65 triggering_scope=triggering_scope,
66 parent_id=parent_id,
67 all_states=all_states,
68 )
70 def to_dict(self) -> Dict:
71 """Export the typed snapshot document payload."""
72 return {
73 "id": self.id,
74 "timestamp": self.timestamp,
75 "label": self.label,
76 "triggering_scope": self.triggering_scope,
77 "parent_id": self.parent_id,
78 "states": {
79 scope_id: {
80 "saved_resolved": ss.saved_resolved,
81 "live_resolved": ss.live_resolved,
82 "parameters": ss.parameters,
83 "saved_parameters": ss.saved_parameters,
84 "provenance": ss.provenance,
85 "construction": ss.construction,
86 "meta": ss.meta,
87 }
88 for scope_id, ss in self.all_states.items()
89 },
90 }
92 @classmethod
93 def from_dict(cls, data: Dict) -> "Snapshot":
94 """Import a typed snapshot document payload."""
95 all_states = {
96 scope_id: StateSnapshot(
97 saved_resolved=state_data["saved_resolved"],
98 live_resolved=state_data["live_resolved"],
99 parameters=state_data["parameters"],
100 saved_parameters=state_data["saved_parameters"],
101 provenance=state_data["provenance"],
102 construction=state_data["construction"],
103 meta=state_data["meta"],
104 )
105 for scope_id, state_data in data["states"].items()
106 }
107 return cls(
108 id=data["id"],
109 timestamp=data["timestamp"],
110 label=data["label"],
111 triggering_scope=data["triggering_scope"],
112 parent_id=data["parent_id"],
113 all_states=all_states,
114 )
117@dataclass
118class Timeline:
119 """Named branch of history - analogous to a git branch.
121 Points to a head snapshot and tracks its base (branch point).
122 """
124 name: str
125 head_id: str # UUID of current head snapshot
126 base_id: str # UUID of snapshot this timeline branched from
127 created_at: float = field(default_factory=time.time)
128 description: str = ""
130 def to_dict(self) -> Dict:
131 """Export to JSON-serializable dict."""
132 return asdict(self)
134 @classmethod
135 def from_dict(cls, data: Dict) -> "Timeline":
136 """Require all declared fields and ignore unknown document keys."""
137 return cls(**{item.name: data[item.name] for item in fields(cls)})