diff --git a/crudkit/core/base.py b/crudkit/core/base.py index 4618a85..3492dcb 100644 --- a/crudkit/core/base.py +++ b/crudkit/core/base.py @@ -1,5 +1,7 @@ -from sqlalchemy import Column, Integer, DateTime, func -from sqlalchemy.orm import declarative_mixin +from sqlalchemy import Column, Integer, DateTime, Boolean, func +from sqlalchemy.orm import declarative_mixin, declarative_base + +Base = declarative_base() @declarative_mixin class CRUDMixin: diff --git a/crudkit/core/service.py b/crudkit/core/service.py index e82bec1..b240a60 100644 --- a/crudkit/core/service.py +++ b/crudkit/core/service.py @@ -4,17 +4,32 @@ from crudkit.core.spec import CRUDSpec T = TypeVar("T") +def _is_truthy(val): + return str(val).lower() in ('1', 'true', 'yes', 'on') + class CRUDService(Generic[T]): def __init__(self, model: Type[T], session: Session): self.model = model self.session = session + self.supports_soft_delete = hasattr(model, 'is_deleted') - def get(self, id: int) -> T: - return self.session.get(self.model, id) + def get(self, id: int, include_deleted: bool = False) -> T | None: + obj = self.session.get(self.model, id) + if obj is None: + return None + if self.supports_soft_delete and not include_deleted and obj.is_deleted: + return None + return obj def list(self, params=None) -> list[T]: query = self.session.query(self.model) + if params: + if self.supports_soft_delete: + include_deleted = False + include_deleted = _is_truthy(params.get('include_deleted')) + if not include_deleted: + query = query.filter(self.model.is_deleted == False) spec = CRUDSpec(self.model, params) filters = spec.parse_filters() order_by = spec.parse_sort() @@ -41,12 +56,25 @@ class CRUDService(Generic[T]): def update(self, id: int, data: dict) -> T: obj = self.get(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} for k, v in data.items(): - setattr(obj, k, v) + if k in valid_fields: + setattr(obj, k, v) self.session.commit() return obj - def delete(self, id: int): - obj = self.get(id) - self.session.delete(obj) + def delete(self, id: int, hard: bool = False): + obj = self.session.get(self.model, id) + if not obj: + return None + + if hard or not self.supports_soft_delete: + self.session.delete(obj) + else: + obj.is_deleted = True + self.session.commit() + return obj diff --git a/muck/models/dbref.py b/muck/models/dbref.py new file mode 100644 index 0000000..493fcbb --- /dev/null +++ b/muck/models/dbref.py @@ -0,0 +1,23 @@ +from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy.orm import relationship +from crudkit.core.base import CRUDMixin, Base + +class Dbref(Base, CRUDMixin): + __tablename__ = "dbref" + + type = Column(String, nullable=False) + name = Column(String, nullable=False) + + owner_id = Column(Integer, ForeignKey("dbref.id")) + location_id = Column(Integer, ForeignKey("dbref.id")) + + owner = relationship("Dbref", remote_side=[CRUDMixin.id], foreign_keys=[owner_id]) + location = relationship("Dbref", remote_side=[CRUDMixin.id], foreign_keys=[location_id]) + + __mapper_args__ = { + "polymorphic_on": type, + "polymorphic_identity": "dbref" + } + + def __str__(self): + return f"#{self.id} ({self.type}): {self.name}"