from sqlalchemy import Column, Integer, DateTime, Boolean, String, JSON, func from sqlalchemy.orm import declarative_mixin, declarative_base Base = declarative_base() @declarative_mixin class CRUDMixin: id = Column(Integer, primary_key=True) created_at = Column(DateTime, default=func.now(), nullable=False) updated_at = Column(DateTime, default=func.now(), nullable=False, onupdate=func.now()) def as_dict(self, fields: list[str] | None = None): """ Serialize mapped columns. Honors projection if either: - 'fields' is passed explicitly, or - """ allowed = None if fields: allowed = set(fields) else: allowed = getattr(self, "__crudkit_root_fields__", None) result = {} for cls in self.__class__.__mro__: if not hasattr(cls, "__table__"): continue for column in cls.__table__.columns: name = column.name if allowed is not None and name not in allowed and name != "id": continue result[name] = getattr(self, name) return result class Version(Base): __tablename__ = "versions" id = Column(Integer, primary_key=True) model_name = Column(String, nullable=False) object_id = Column(Integer, nullable=False) change_type = Column(String, nullable=False) data = Column(JSON, nullable=True) timestamp = Column(DateTime, default=func.now()) actor = Column(String, nullable=True) meta = Column('metadata', JSON, nullable=True)