Initial SFERA platform baseline
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from sir import SemanticNode, SirSnapshot, stable_hash
|
||||
|
||||
|
||||
class SemanticObjectVersion(BaseModel):
|
||||
version_id: str
|
||||
lineage_id: str
|
||||
semantic_id: str
|
||||
object_hash: str
|
||||
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
task_id: str | None = None
|
||||
session_id: str | None = None
|
||||
parent_version_id: str | None = None
|
||||
payload: dict = Field(default_factory=dict)
|
||||
|
||||
|
||||
class SemanticObjectDiffEntry(BaseModel):
|
||||
path: str
|
||||
kind: str
|
||||
before: object | None = None
|
||||
after: object | None = None
|
||||
|
||||
|
||||
class SemanticObjectDiff(BaseModel):
|
||||
from_version_id: str
|
||||
to_version_id: str
|
||||
lineage_id: str
|
||||
changed: bool
|
||||
entries: list[SemanticObjectDiffEntry] = Field(default_factory=list)
|
||||
|
||||
|
||||
class SemanticObjectChange(BaseModel):
|
||||
change_kind: str
|
||||
diff: SemanticObjectDiff
|
||||
|
||||
|
||||
class InMemoryObjectVersionStore:
|
||||
def __init__(self) -> None:
|
||||
self._versions_by_lineage: dict[str, list[SemanticObjectVersion]] = {}
|
||||
|
||||
def append_node_version(
|
||||
self,
|
||||
node: SemanticNode,
|
||||
*,
|
||||
task_id: str | None = None,
|
||||
session_id: str | None = None,
|
||||
) -> SemanticObjectVersion:
|
||||
previous = self.latest(node.lineage_id)
|
||||
payload = node.model_dump(mode="json")
|
||||
object_hash = stable_hash(json.dumps(payload, ensure_ascii=False, sort_keys=True))
|
||||
version = SemanticObjectVersion(
|
||||
version_id=f"version.{node.lineage_id}.{object_hash}",
|
||||
lineage_id=node.lineage_id,
|
||||
semantic_id=node.semantic_id,
|
||||
object_hash=object_hash,
|
||||
task_id=task_id,
|
||||
session_id=session_id,
|
||||
parent_version_id=previous.version_id if previous else None,
|
||||
payload=payload,
|
||||
)
|
||||
versions = self._versions_by_lineage.setdefault(node.lineage_id, [])
|
||||
if not versions or versions[-1].object_hash != object_hash:
|
||||
versions.append(version)
|
||||
return versions[-1]
|
||||
|
||||
def append_snapshot_versions(
|
||||
self,
|
||||
snapshot: SirSnapshot,
|
||||
*,
|
||||
task_id: str | None = None,
|
||||
session_id: str | None = None,
|
||||
) -> list[SemanticObjectVersion]:
|
||||
return [
|
||||
self.append_node_version(node, task_id=task_id, session_id=session_id)
|
||||
for node in snapshot.nodes
|
||||
]
|
||||
|
||||
def upsert_version(self, version: SemanticObjectVersion) -> SemanticObjectVersion:
|
||||
versions = self._versions_by_lineage.setdefault(version.lineage_id, [])
|
||||
if all(existing.version_id != version.version_id for existing in versions):
|
||||
versions.append(version)
|
||||
versions.sort(key=lambda item: item.created_at)
|
||||
return version
|
||||
|
||||
def latest(self, lineage_id: str) -> SemanticObjectVersion | None:
|
||||
versions = self._versions_by_lineage.get(lineage_id, [])
|
||||
return versions[-1] if versions else None
|
||||
|
||||
def find_version(self, version_id: str) -> SemanticObjectVersion | None:
|
||||
return next((version for version in self.all_versions() if version.version_id == version_id), None)
|
||||
|
||||
def history(self, lineage_id: str) -> list[SemanticObjectVersion]:
|
||||
return list(self._versions_by_lineage.get(lineage_id, []))
|
||||
|
||||
def all_versions(self) -> list[SemanticObjectVersion]:
|
||||
return [
|
||||
version
|
||||
for versions in self._versions_by_lineage.values()
|
||||
for version in versions
|
||||
]
|
||||
|
||||
|
||||
def diff_versions(
|
||||
before: SemanticObjectVersion,
|
||||
after: SemanticObjectVersion,
|
||||
) -> SemanticObjectDiff:
|
||||
if before.lineage_id != after.lineage_id:
|
||||
raise ValueError("Cannot diff versions from different lineage_id values")
|
||||
entries = _diff_payload("", before.payload, after.payload)
|
||||
return SemanticObjectDiff(
|
||||
from_version_id=before.version_id,
|
||||
to_version_id=after.version_id,
|
||||
lineage_id=before.lineage_id,
|
||||
changed=bool(entries),
|
||||
entries=entries,
|
||||
)
|
||||
|
||||
|
||||
def classify_version_change(
|
||||
before: SemanticObjectVersion,
|
||||
after: SemanticObjectVersion,
|
||||
) -> SemanticObjectChange:
|
||||
diff = diff_versions(before, after)
|
||||
if not diff.changed:
|
||||
change_kind = "UNCHANGED"
|
||||
elif _object_parent(before.payload.get("qualified_name")) != _object_parent(after.payload.get("qualified_name")):
|
||||
change_kind = "MOVE"
|
||||
elif before.payload.get("name") != after.payload.get("name"):
|
||||
change_kind = "RENAME"
|
||||
else:
|
||||
change_kind = "UPDATE"
|
||||
return SemanticObjectChange(change_kind=change_kind, diff=diff)
|
||||
|
||||
|
||||
def _object_parent(qualified_name: object) -> str:
|
||||
if not isinstance(qualified_name, str) or "." not in qualified_name:
|
||||
return ""
|
||||
return qualified_name.rsplit(".", 1)[0]
|
||||
|
||||
|
||||
def _diff_payload(path: str, before: object, after: object) -> list[SemanticObjectDiffEntry]:
|
||||
if isinstance(before, dict) and isinstance(after, dict):
|
||||
entries: list[SemanticObjectDiffEntry] = []
|
||||
for key in sorted(set(before) | set(after)):
|
||||
child_path = f"{path}.{key}" if path else str(key)
|
||||
if key not in before:
|
||||
entries.append(SemanticObjectDiffEntry(path=child_path, kind="ADD", after=after[key]))
|
||||
elif key not in after:
|
||||
entries.append(SemanticObjectDiffEntry(path=child_path, kind="REMOVE", before=before[key]))
|
||||
else:
|
||||
entries.extend(_diff_payload(child_path, before[key], after[key]))
|
||||
return entries
|
||||
if before == after:
|
||||
return []
|
||||
return [SemanticObjectDiffEntry(path=path or "$", kind="CHANGE", before=before, after=after)]
|
||||
|
||||
|
||||
__all__ = [
|
||||
"InMemoryObjectVersionStore",
|
||||
"SemanticObjectDiff",
|
||||
"SemanticObjectDiffEntry",
|
||||
"SemanticObjectChange",
|
||||
"SemanticObjectVersion",
|
||||
"classify_version_change",
|
||||
"diff_versions",
|
||||
]
|
||||
Reference in New Issue
Block a user