Getting more patch logic in.

This commit is contained in:
Yaro Kasear 2025-10-01 09:38:40 -05:00
parent cab35b72ec
commit c040ff74c9
5 changed files with 196 additions and 175 deletions

View file

@ -10,6 +10,7 @@ from sqlalchemy.orm.attributes import InstrumentedAttribute
from sqlalchemy.sql import operators
from sqlalchemy.sql.elements import UnaryExpression, ColumnElement
from crudkit.core import deep_diff, diff_to_patch
from crudkit.core.base import Version
from crudkit.core.spec import CRUDSpec
from crudkit.core.types import OrderSpec, SeekWindow
@ -660,16 +661,41 @@ class CRUDService(Generic[T]):
session = self.session
obj = session.get(self.model, id)
if not obj:
raise ValueError(f"{self.model.__name__} with ID {id} not found.")
valid_fields = {c.name for c in self.model.__table__.columns}
unknown = set(data) - valid_fields
raise ValueError("f{self.model.__name__} id ID {id} not found.")
# Only touch real columns
valid = {c.name for c in self.model.__table__.columns}
unknown = set(data) - valid
if unknown:
raise ValueError(f"Unknown fields: {', '.join(sorted(unknown))}")
# BEFORE snapshot (non-lazy, columns-only is fine)
before = obj.as_dict()
# Apply patch
for k, v in data.items():
if k in valid_fields:
setattr(obj, k, v)
setattr(obj, k, v)
# If nothing changed at ORM level, bail early
if not session.is_modified(obj, include_collections=False):
return obj
session.commit()
self._log_version("update", obj, actor)
after = obj.as_dict()
diff = deep_diff(
before,
after,
ignore_keys={"updated_at", "created_at", "id"},
list_mode="index",
)
# If somehow no diff, do not spam versions
if not(diff["added"] or diff["removed"] or diff["changed"]):
return obj
self._log_version("update", obj, actor, metadata={"diff": diff})
return obj
def delete(self, id: int, hard: bool = False, actor = None):
@ -688,17 +714,19 @@ class CRUDService(Generic[T]):
def _log_version(self, change_type: str, obj: T, actor=None, metadata: dict | None = None):
session = self.session
snapshot = {}
try:
data = obj.as_dict()
snapshot = obj.as_dict()
except Exception:
data = {"error": "Failed to serialize object."}
snapshot = {"error": "serialize failed"}
version = Version(
model_name=self.model.__name__,
object_id=obj.id,
change_type=change_type,
data=data,
data=snapshot,
actor=str(actor) if actor else None,
meta=metadata
meta=metadata or None,
)
session.add(version)
session.commit()